0. 概述
在清理收藏夹的时候发现了一篇美团技术团队分享的关于分布式 ID 的文章,同时,最近看到他们开源了他们的实现,所以就消化了一下,原文我觉得写得有点冗长和不清晰,所以我就顺带总结了一下,内容就列举在下面吧。
总的来说,对于分布式 ID 的要求主要有以下几个方面:
- 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
- 递增性:对于分配的 ID,需要根据申请的顺序保持增序
- 安全性:ID 不具有完全的规律让人发现分配 ID 的数量,同时又不存在已知安全风险
- 高可用:不能出现无法分配 ID 的情况,或者尽可能出现的时间短(5个9以上?)
- 扩展性:系统性能可以扩展
然后就根据这些要求我对提及到的算法和方式进行一个归纳:
1. UUID
UUID 有多个版本,每个版本的生成方式略有不同,到目前为止,总共有 5 个版本,但是无论是哪个版本,他们的表示都是一致的,以 16 进制表示可以分为 5 组:
[root@liqiang.io]# 8–4–4–4–12
[root@liqiang.io]# 8ca0fd81-fd03-438c-8730-c6c4e7ef4aa9
其中有两个特殊的位置是表示使用的 UUID 的版本的:
[root@liqiang.io]# xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
不同版本生成的 UUID 的规则:
- 版本 1 : UUID 是根据时间(不是 Unix 时间戳,而是距离 1581/10/15 的某个时间点的时间)和节点 ID(通常是 MAC 地址)生成;
- 版本 2 : UUID 是根据标识符(通常是组或用户 ID)、时间和节点 ID 生成;因为有一些细节没有公开,所以有些 UUID 实现直接不提供这个版本;
- 版本 3、版本 5 : 版本 5 - 确定性 UUID 通过散列(hashing)名字空间(namespace)标识符和名称生成;版本 3 使用 MD5,版本 5 使用 SHA-1;
- 版本 4 : UUID 使用随机性[6]或伪随机性[7]生成,所以和网卡、Node 信息无关,但是特定的位上还是包含了版本信息;
优缺点
UUID 优点:
- 全局唯一得到满足
- 隐蔽性得到满足
- 无需依赖,直接生成,高可用得到满足
- 无需扩展,扩展性得到满足
UUID 缺点:
- 长度太长,不适合传输和存储,MySQL 下 InnoDB 索引不友好
- 递增性无法得到满足
- 安全性方面存在一些一致风险
2. 数据库生成
通过一个设置一个 MySQL 的自增 ID 字段,每次需要 ID 的时候就从这个字段获取下一个可用的值。
- G:
- 全局唯一性得到满足
- 递增性得到满足
- B:
- 安全性无法满足
- 非高可用,MySQL 单机宕机之后服务不可用
- 无法扩展,系统性能瓶颈在 MySQL 单机
数据库生成扩展
将单机的 MySQL 扩展为 MySQL 集群,总共 N 台机器,每台集群设置不同的起始 ID(0,1,2…,N-1),然后设置一样的增长步长(N),添加一层分发器材从轮询不通的机器获取下一个 ID:
- G:
- 全局唯一性得到满足;
- 递增性得到满足
- 高可用得到满足
- B:
- 高可用和全局递增性无法同时满足;
- 扩展性不佳;
- 系统瓶颈还是在于 MySQL 瓶颈
3. snowflake
snowflake 是 twitter 发表的分布式 ID 算法,生成算法的方式为:
- 前面 41 位长的时间序列,可以精确到毫秒
- 中间 10 位的机器标识,最多支持部署1024个节点
- 后面 12 位的序列号,支持每个节点每毫秒产生 4096 个ID序号
通过这种方式:
- G:
- 全局唯一性得到满足;
- 递增性得到满足(单机递增,全局不递增)
- 高可用得到满足;
- 扩展性得到满足;
- B:
- 依赖于系统时钟,在系统时钟出现回拨的时候会出现 ID 重复;
启动流程
segment-id
因为每次 ID 都从 DB 直接获取容易出现 DB 的瓶颈,所以这里的解决方案是开一个组件预取一段 ID,用于分发:
但是这个还是有以下缺点:
- ID号码不够随机,能够泄露发号数量的信息,不太安全。
- TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。
- DB宕机会造成整个系统不可用。
美团的文章只给了第 2 个的解决方案,那就是引入双层 buffer:
时钟回拨解决
- 回拨时间小的时候,不生成 ID,循环等待到时间点到达。
- 如果间隔过大,阻塞等待,肯定是不可取的,因此要么超过一定大小的回拨直接报错,拒绝服务,或者有一种方案是利用拓展位,回拨之后在拓展位上加1就可以了,这样ID依然可以保持唯一。但是这个要求我们提前预留出位数,要么从机器id中,要么从序列号中,腾出一定的位,在时间回拨的时候,这个位置
我的个人看法:可以不可以在时钟回拨之后重新注册,分配一个新的 worker id?
5. 总结
从上面 3 种方案来看,twitter 的 snowflake 是一种比较好的方法,明显的缺点就只有一个了,那就是在时钟回拨的时候会出现问题。而通过 MySQL 的方案呢,主要缺点在于扩展性和性能瓶颈上,所以美团的技术团队针对这两个问题提出了两个解决方案。
这篇文章虽然没有对我们暴露所有的技术细节,但是也是了解了美团对分布式 ID 上的实践。这里还是有一些问题值得我去思考一番:
- 有没有更好的方式能满足高性能和足够扩展性的全局递增的 ID 分配?
- DB 宕机会造成整个系统不可用,这个对于美团真的是问题吗?还是说这个可以由 DBA 团队来满足;
- 需要用 DB 去做 ID 分配这个轻的活吗?还是可以直接自己写个组件?
以上都是我个人的瞎猜,如果你想看关于文章的更多细节,不妨点下方的原文连接进行阅读。