「这是我参与2022首次更文挑战的第 7 天,活动详情查看:2022首次更文挑战」。
《系统日报》持续关注分布式系统、AI System,数据库、存储、大数据等相关领域文章。每天以摘要的形式精选不超过三篇系统文章分享给大家。 如果你有好文章推荐,或者有其他任何想法,欢迎在 Articles Weekly Repo提 issue。
分布式高性能 ID 生成算法——Snowflake ID
来源:blog.twitter.com/engineering…
摘要:雪花算法(Snowflake ID) 是时下应用相当广的一个分布式全序 ID 生成算法,这篇 Twitter 官方博客 2010 年的文章,宣告了雪花算法的诞生,概略的介绍了 Twitter 当时对分布式 ID 生产算法需求的背景(context)、可选项和最终方案。理解了其产生时的需求,也就理解了算法的一半,推荐一读。
问题背景。Twitter 的数据库经历了一个从小到大、从单机到分布式的增长过程。但无论在分布式数据库 Cassandra 中,还是使用 gizzard 方案水平扩容的多机 MySQL 中,都没有一个满足 Twitter 当时需求的全局 ID 生成方案。那 Twitter 当时对全局 ID 的要求是什么呢?
- 每秒产生数万个 ID。这就限制了不能使用依赖多机沟通来产生 ID。毕竟一次网络通信延迟都会有 1ms+,自然难以实现超过 1k 的 qps。
- 所有 ID 满足全序(roughly sortable)关系。即任意两个 id 都是可比的,毕竟 Feed 流中所有 Tweet 的排序都依赖此 ID。
- 长度不超过 64 bit 。Twitter 以前经历过随着系统体量的增大而痛苦的增加 ID bit 数的过程,这次希望一步到位,但同时又不太长。
可选项。基于 MySQL 的自增 id,类似于 flickr 的方案。但不通过多机同步,难以提供全序保证。
一些现成的 UUID 算法,但其生成的 ID 都是 128 bit。
基于 Zookeeper 的全局自增 id 。自然,由于 Zab 等共识算法,其能保证全序,却不能满足 Twitter 的性能需求。
最终方案。通过组合时间戳、进程编号、自增序号,Twitter 找到了一种分布式高性能全序 ID 生成算法。基本思想是,大体保证机器间的时钟同步,并利用机器时钟生成时间戳作为自增 ID,如果两个进程产生了相同时间戳,则通过进程编号进一步确认其大小。由于一般时间戳会精确到毫秒,为了满足 QPS 需求,会留几位给自增 id,使得 1ms 内产生 10+ 个 id。
最终格式如上图,1 bit 的符号位,固定为0,以保证在有符号数体系下 ID 也为正数。41 bit 的时间戳,单位 ms,时间戳本身是个相对值,其起始点可以自行设置。10 bit 的进程编号,最终可支持含有 1024 个进程的单机或者集群,但一般来说,每个机器一个进程。12 bit 的自增序号,每毫秒最多允许产生 4k+ 个 ID。
在实际使用时,可以在保证总 bit 数的情况下,按需调整三个字段的 bit 数。比如进程数确定不会超过 100 个,则可以将对应字段缩短为 7 bit。进程序号可以在初始时通过一个全局发号器来分配,比如 Zookeeper。在之后的运行或者重启时,无需再改。
开源代码在此,其优点如下:
- 高性能(Performance)。单进程 10k+ 的 QPS,平均 2ms 的延迟。
- 无需沟通(Uncoordinated)。产生 ID 的这组进程,可以分布在多个数据中心的多个机器,而在产生数据时无进行互相沟通(除了 NTP 时间戳同步)。
- 大致按时间有序(Roughly Time Ordered)。可以从 ID 中解析出时间戳。
- 可直接排序(Directly Sortable)。无需解析即可直接排序。
- 紧凑(Compact)。不要 128 bit 就要 64 bit。
- 高可用(Highly Available)。将进程分布在多数据中心的多台机器上,只要有一台机器活着,就仍能提供服务。
一些问题。雪花算法会隐式的依赖机器时钟,虽然并不严格。但使用者需要保证产生 ID 的所有机器通过 NTP 保持大致的时间同步。snowflake 算法可以处理由于 NTP 时钟同步带来的时钟回退问题。但解决方法很粗暴,即发现时钟回退了就死等到时钟超过上一次 ID 产生的对应时间点。也正因如此,需要维持所有机器时钟大致同步。
题图故事