前言
看了一下美团的分布式ID的解决方案,谈谈自己的理解和思考。其中参考博客就是美团的分布式ID leaf的链接,可以直接跳转去看。
Leaf-segment 数据库方案
这里采用的是从数据库读取,每次从数据库里读取id起始点和步长,比如读取id为1000,步长为1000,那么可以生成的分布式id范围为1000 - 2000 。但不仅仅是这么简单的数字,一般形式如下:
biz_tag + id
- biz_tag:业务标识符,一般为字符串,表明业务
- id:就是我们说的id数值
上述两个合在一起就是一个分布式ID,所以我们的分布式ID 数据库表设计为:
| 表字段 | 解释 |
| ---- | ---- |
|id| 主键id ,该条记录的id |
| biz_tag | 业务标识符,一般为字符串,表明业务 |
| max_id | 就是我们说的id数值,也是起始点 |
| step | 步长,用来表明范围 |
| desc | 描述信息 |
| update_time | 更新时间 |
Begin
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx//先更新
SELECT tag, max_id, step FROM table WHERE biz_tag=xxx//查询
Commit
优点:
- Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景,比如业务扩展,可能其他场景也会用到。
- ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。
- 容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。
- 可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。
- ID号码不够随机,能够泄露发号数量的信息,不太安全,连续增长的,下一个id可以被预见。
- TP999数据波动大,当号段使用完之后还是会hang在更新数据库的 I/O 上,tg 999 数据会出现偶尔的尖刺。
- DB宕机会造成整个系统不可用,其实可以持续一段时间,不至于立马不可用,因为有缓存,缓存中有id范围,只有当id被用完了,从数据库中读取新的id时才会导致系统不可用。
- 当id用完了,要读取数据库时,这个时候大量请求来了,都会堵塞。
【分布式知识|美团 Leaf分布式ID解决方案】针对前一个Leaf - segment 方案中,有大量请求时存在堵塞情况,有人就提出了双buffer的方法,来优化这个逻辑,双buffer听起来很奇怪,简单描述说就是两个数组,每个数组里都有id初始值和步长,用完了一个数组,就用另一个数组,大概流程就是:
- 1、在A数组里生成id
- 2、当A数组还剩下80%的 id 可用的时候哦,查看B数组是否有可用Id,
如果没有,就异步线层去数据库里读,
如果有,就没事 - 3、A数组Id用完了就读取B数组,同样也去更新A数组内容。
- 4、 这样来回循环
文章图片
每个biz-tag都有消费速度监控,通常推荐segment长度设置为服务高峰期发号QPS的600倍(10分钟),这样即使DB宕机,Leaf 仍能持续发号10-20分钟不受影响。
每次请求来临时都会判断下个号段的状态,从而更新此号段,所以偶尔的网络抖动不会影响下个号段的更新。
Leaf-snowflake服务
Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,不适用于订单ID生成场景,比如对在两天中午12点分别下单,通过订单id号相减就能大致计算出公司一天的订单量,这个是不能忍受的。面对这一问题,我们提供了 Leaf-snowflake 方案。Leaf-snowflake方案完全沿用snowflake方案的bit位设计,即是“1+41+10+12”的方式组装ID号。
文章图片
Leaf服务规模较大,动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID,然后把IP + Port 作为临时节点,有可能wokerID下面有多个机器,每个机器都有一个IP。
其中也讨论了关于时钟回拨的问题,将系统时间写入到zookeeper中,代码对时间进行判断是否有回拨问题。但是我很奇怪它将系统时间写入到workID下面,而不是机器IP下面,不过猜想有可能是因为系统时间只要有一个就行了,不需要多个,因为对于workID来说,业务来讲只要有一个时间戳就行,如果workID是一个集群,那么处理这个请求的也只有一台机器。所以有可能是基于这个考虑记录到workID下。
参考博客
Leaf——美团点评分布式ID生成系统