一, 问题描述
分布式系统, 怎么生成全局unique ID? 单机自增 ID 就可以
(1)全局唯一、Shards 之间迁移不会受到 ID 生成方式的限制
(2)带上时间, 例 ID 的前 k 位是 Timestamp, 按时间排序
(3)不大于 64 bits,速度有要求. 例如, 在一个高吞吐量的场景中, 需要每秒生成几万个 ID (Twitter 最新的峰值到达了143,199 Tweets/s, 也就是 10万+/秒)
(4)整个服务最好没有单点
so,就不可以:UUID.randomUUID() ,128 bits, 没有Timestamp
中心服务器统一生成 :解决单点问题; 支持高吞吐率(例,从中心服务器批量获取一批 IDs, 提升 ID 产生吞吐率)
Flickr 的做法 (http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/). 但他这个方案 ID 中没有带 Timestamp, 生成的 ID 不能按时间排序
二, 解决办法 :Twitter Snowflake
https://github.com/twitter/snowflake Snowflake 生成的 unique ID 的组成 (由高位到低位、63 bits最高位 0)):
Timestamp (毫秒级,41 bits) + 节点 ID (datacenter ID 5 bits + worker ID 5 bits) + sequence number(12 bits)
unique ID 生成过程:
10 bits 的机器号, 在 ID 分配 Worker 启动的时候, 从一个 Zookeeper 集群获取 (保证所有的 Worker 不会有重复的机器号)
Timestamp: 生成新 ID时, 获取当前 Timestamp,
sequence number 分两种情况:
(1)当前的 Timestamp 和前一个在同一毫秒中, 就用前一个 ID 的 sequence number + 1 作为新的 sequence number (12 bits); 如果本毫秒内的所有 ID 用完, 等到下一毫秒继续 (这个等待过程中, 不能分配出新的 ID)
(2)当前的 Timestamp 比前一个 ID 的 Timestamp 大, 随机生成一个初始 sequence number (12 bits) 作为本毫秒内的第一个 sequence number 整个过程中, 只是在 Worker 启动的时候会对外部有依赖 (需要从 Zookeeper 获取 Worker 号), 之后就可以独立工作了, 做到了去中心化.
是两个都要依赖吗?
异常情况讨论:
如果获取到时间戳比前一个已生成 ID 的 Timestamp 小怎么办? Snowflake 继续获取当前机器时间, 直到获取到更大的 Timestamp 才能继续工作 (等待过程, 不能分配出新的 ID)
如果 Snowflake 所运行机器时钟有大偏差时, 整个 Snowflake 系统不能正常工作 (偏差得越多, 分配新 ID 时等待的时间越久)
Snowflake 官方文档 (https://github.com/twitter/snowflake/#system-clock-dependency) 明确要求 "You should use NTP to keep your system clock accurate". 且最好把 NTP 配置成不会向后调整的模式., NTP 纠正时间时, 不会向后回拨机器时钟.
三, Snowflake 的其他变种
1. Boundary flake
http://boundary.com/blog/2012/01/12/flake-a-decentralized-k-ordered-unique-id-generator-in-erlang/
变化:
ID 长度扩展到 128 bits: 最高 64 bits 时间戳; 然后是 48 bits 的 Worker 号 (和 Mac 地址一样长); 最后是 16 bits 的 Seq Number 由于它用 48 bits 作为 Worker ID, 和 Mac 地址的长度一样,
启动时不需 Zookeeper 的Worker ID. 完全去中心化 基于 Erlang 它这样做的目的是用更多的 bits 实现更小的冲突概率, 这样就支持更多的 Worker 同时工作. 同时, 每毫秒能分配出更多的 ID
2. Simpleflake
http://engineering.custommade.com/simpleflake-distributed-id-generation-for-the-lazy/
取消 Worker 号,
保留 41 bits 的 Timestamp(和 Snowflake 一致),
sequence number 扩展到 22 bits; 完全靠随机产生 (可能出现重复)
限制就每秒生成ID 不能太多 (最好小于 100次/秒, 如果大于 100次/秒的场景, Simpleflake 就不适用了, 切换回 Snowflake).
3. instagram 的做法
Table 分多个逻辑分片 (logic Shard), 数量可很大, 如 2000
被存储到哪个数据库实例上面; 数据库实例不需要很多. 例如, 对有 2 个 PostgreSQL 实例的系统 (instagram 使用 PostgreSQL); 可以使用奇数逻辑分片存放到第一个数据库实例, 偶数逻辑分片存放到第二个数据库实例的规则
每个 Table 指定一个字段作为分片字段 (例如, 对用户表, 可以指定 uid 作为分片字段)
插入一个新的数据时, 先根据分片字段的值, 决定数据被分配到哪个逻辑分片 (logic Shard)
然后再根据 logic Shard 和 PostgreSQL 实例的对应关系, 确定这条数据应该被存放到哪台 PostgreSQL 实例上
unique ID 的组成:
41 bits: Timestamp (毫秒)
13 bits: 每个 logic Shard 代号 (最大支持 8 x 1024 个 logic Shards)
10 bits: sequence number; 每个 Shard 每毫秒最多可以生成 1024 个 ID
logic Shard 代号:
插入新记录, 根据 uid 来判断这条记录应该被插入到哪个 logic Shard 中.
假设当前要插入的记录会被插入到第 1341 号 logic Shard 中 (假设当前的这个 Table 一共有 2000 个 logic Shard)
新生成 ID 的 13 bits 段要填的就是 1341 这个数字
sequence number 利用 PostgreSQL 每个 Table 上的 auto-increment sequence 来生成:
如果当前表上已经有 5000 条记录, 那么这个表的下一个 auto-increment sequence 就是 5001 (直接调用 PL/PGSQL 提供的方法可以获取到)
然后把 这个 5001 对 1024 取模就得到了 10 bits 的 sequence number
优势: logic Shard 替换 Worker 号,知道被存放在哪,按 logic Shard 为单位迁移
推荐一篇文章:微信的海量IM聊天消息序列号生成
https://www.cnblogs.com/imstudy/p/9766549.html
https://zhuanlan.zhihu.com/p/59289093
问题:https://www.cnblogs.com/keeya/p/9347617.html 怎么实现全局递增的唯一ID?改进办法没看明白