- 参考博客:Leaf:美团分布式ID生成服务开源
Leaf是美团基础研发平台推出的一个分布式ID生成服务
一、Leaf 号段生成
1、Leaf 特性
- 全局唯一:不会出现重复 ID,且 ID 整体趋势递增
- 高可用:基于分布式架构,即使 MySQL 宕机,也能容忍一段时间的数据库不可用
- 高并发低延时:在 CentOS 4C8G 的虚拟机上,远程调用QPS可达5W+,TP99在1ms内
- 接入简单:可通过 RPC 服务或 HTTP 调用接入
2、Leaf 详解
- Leaf 采用预分发方式生成 ID:即在 DB 上挂 N 个 Server,每个 Server 启动时,都去 DB 拿固定长度的 ID List,因此完全基于分布式的架构
同时 ID 由内存分发,所以也很高效
- Leaf 数据持久化:每次去 DB 拿固定长度的 ID List,然后把最大的 ID 持久化下来
即:并非每个 ID 都做持久化,仅仅持久化一批 ID 中最大的那个,极大减轻了 DB 持久化的压力
用户通过
Round-robin
方式调用 Leaf Server 的各个服务,当某个 Leaf Server 号段用完后,下一次请求就会从 DB 中加载新的号段,保证了每次加载的号段递增
Leaf Server 加载号段的 SQL 语句如下:
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
问题:
- 更新 DB 时会出现耗时尖刺,系统最大耗时取决于更新 DB 号段的时间
- 更新 DB 号段时,若 DB 宕机或发生主从切换,会导致一段时间的服务不可用
3、Leaf 优化
(1) Leaf 双 Buffer 优化
解决上述问题:采用异步更新策略,通过双 Buffer 方式,保证无论何时 DB 出现问题,都能有一个 Buffer 号段可以正常对外提供服务,只要 DB 在一个 Buffer 的下发的周期内恢复,就不会影响整个 Leaf 的可用性
问题:
- 号段长度固定:若 Leaf 能在 DB 不可用时维持10分钟正常工作,但若流量增加10倍就只能维持1分钟正常工作了
- 若号段长度设置的过长,导致缓存中的号段迟迟消耗不完,进而导致更新 DB 的新号段与前一次下发的号段ID跨度过大
(2) Leaf 动态调整 Step 优化
-
问题分析:假设服务 QPS 为 Q,号段长度为 L,号段更新周期为 T,则
Q * T = L
- 最开始 L 长度固定,导致随着 Q 的增长,T 会越来越小
- 但 Leaf 希望 T 固定,因此若 L 可以和 Q 正相关,则 T 就可以趋近一个定值
-
解决方案:Leaf 每次更新号段时,根据上一次更新号段的周期 T 和号段长度 step,来决定下一次的号段长度 nextStep
T < 15min,nextStep = step * 2
15min < T < 30min,nextStep = step
T > 30min,nextStep = step / 2
问题:若流量瞬时几十、几百倍的暴增,仍不能满足容忍数据库在一段时间不可用、系统仍能稳定运行的需求
(3) MySQL 高可用优化
- 问题分析:Leaf 虽然在 DB 层做了些容错方案,但号段方式的 ID 下发,最终还是强依赖 DB
- 解决方案:在 MySQL 层,Leaf 目前采取半同步方式同步数据,未来追求完全的强一致
Leaf 采用一个临时方案来保证机房断网场景下的数据一致性:
- 多机房部署数据库,每个机房一个实例,保证都是跨机房同步数据
- 半同步超时时间设置到无限大,防止半同步方式退化为异步复制
(4) 未来优化
- 号段加载优化:
- 目前方案:Leaf 目前重启后的第一次请求还是同步加载 MySQL
原因:MySQL 中的 Leaf Key 并非一定都被这个 Leaf 服务节点所加载,若每个 Leaf 节点都在初始化加载所有的Leaf Key 会导致号段的大量浪费
- 未来方案:在 Leaf 服务 Shutdown 时,备份这个服务节点近一天使用过的 Leaf Key 列表,这样重启后会预先从MySQL 加载 Key List 中的号段
- 目前方案:Leaf 目前重启后的第一次请求还是同步加载 MySQL
- 单调递增:只要保证同一时间、同一个 Leaf Key 都从一个 Leaf 服务节点获取 ID,即可保证递增
注意:Leaf 服务节点切换时,旧 Leaf 服务用过的号段需要废弃
方案:路由逻辑,可采用主备的模型或每个 Leaf Key 配置路由表的方式来实现
二、Leaf 雪花算法生成
通过时间+机器号+自增 ID的组合来实现完全分布式的 ID:
- 第
1
位为 0 - 第
2-42
位是相对时间戳,通过当前时间戳减去一个固定的历史时间戳生成 - 第
43-52
位是机器号 workerID,每个 Server 的机器 ID 不同 - 第
53-64
位是自增 ID
注:Leaf 在第一次从 Zookeeper 拿取 workerID 后,会在本机文件系统上缓存一个 workerID 文件,即使 ZooKeeper 出现问题,同时恰好机器也在重启,也能保证服务的正常运行
三、号段与雪花的对比
- 号段模式:低位趋势增长,较少的 ID 号段浪费,能够容忍 MySQL 的短时间不可用
- Snowflake 模式:完全分布式,ID 有语义
github 仓库:Meituan-Dianping/Leaf