分布式ID生成器-雪花算法Snowflake
总结摘要
分布式系统如何生成唯一ID
雪花算法是 Twitter 开源的一种分布式 ID 生成算法。它是一个非常经典且广泛应用于分布式系统中的唯一 ID 生成方案。
核心思想非常直观:使用一个 64 位的 Long 类型数字作为全局唯一 ID。
下面我为你详细拆解它的原理、结构以及优缺点。
1. ID 结构拆解(核心原理)
Snowflake 生成的 ID 是一个 64 位的整数(Long 类型),这 64 个 bit 位被划分成了四个部分:
让我们从高位到低位逐一分析:
第一部分:符号位 (1 bit)
- 占用: 1 位。
- 含义: 因为 Long 类型在 Java 中是有符号的,最高位是符号位。正数是 0,负数是 1。
- 规则: ID 一般为正数,所以这一位固定为 0。
第二部分:时间戳 (41 bit)
- 占用: 41 位。
- 含义: 这里存储的是时间戳的差值(当前时间戳 - 开始时间戳)。
- 精度: 毫秒级。
- 计算: 41 位能存储的最大数字是 $2^{41} - 1$,大约是 69 年。
- 如果你设定的开始时间戳是近期的(如 2020年),那么这个算法可以使用 69 年不溢出。
- 作用: 保证了 ID 的趋势递增(按时间有序),这对于数据库索引非常友好。
第三部分:机器 ID (10 bit)
- 占用: 10 位。
- 含义: 用于分布式部署。这 10 位通常被拆分为两部分:
- 5 位 数据中心 ID (Datacenter ID):最多支持 32 个数据中心。
- 5 位 工作机器 ID (Worker ID):每个数据中心最多支持 32 台机器。
- 总和: 最多支持 $2^{10} = 1024$ 个节点同时生成 ID。
- 作用: 保证了在分布式环境下,不同机器生成的 ID 不会冲突。
第四部分:序列号 (12 bit)
- 占用: 12 位。
- 含义: 同一毫秒内,同一台机器生成的 ID 序号。
- 计算: 12 位能表示的最大数字是 $2^{12} - 1 = 4095$。
- 作用: 保证了同一毫秒内的高并发生成能力。也就是说,单机每毫秒最多能生成 4096 个 ID。
2. 算法流程逻辑
Snowflake 算法的核心逻辑是一个简单的死循环或同步方法,大致步骤如下:
- 获取当前时间戳。
- 判断时钟回拨:如果当前时间小于上一次生成 ID 的时间,说明时钟倒退了(时钟回拨问题),需要报错或处理。
- 判断是否在同一毫秒:
- 如果是同一毫秒:
- 序列号
sequence自增 1。 - 如果序列号溢出(超过 4095),则循环等待直到下一毫秒。
- 序列号
- 如果不是同一毫秒:
- 序列号重置为 0。
- 如果是同一毫秒:
- 计算 ID:将 时间戳、机器ID、序列号 通过位运算拼接在一起。
简易代码逻辑演示(伪代码):
| |
3. 算法的优缺点
优点:
- 高性能高可用:不依赖数据库、Redis 等外部组件,完全在内存中生成,速度极快。
- 趋势递增:基于时间戳,ID 是随时间递增的,这对 B+ 树索引结构的数据库(如 MySQL)非常友好,写入性能高,减少页分裂。
- 分布式唯一:通过机器 ID 区分,解决了分布式系统的唯一性问题。
- 不连续:虽然有趋势,但 ID 不完全连续,攻击者难以根据 ID 推测业务量。
缺点:
- 时钟回拨问题(最大痛点):
- Snowflake 强依赖服务器时间。如果服务器时间被人为回调,或者发生了 NTP 时间校准导致时间倒退,可能会导致 ID 重复。
- 解决方案:通常的做法是如果回拨时间很短,等待时间追平;如果时间较长,直接报错或利用备用位进行处理。
- 机器 ID 分配问题:
- 在微服务架构下,服务实例动态伸缩,需要一套机制来管理 WorkerID 的分配(例如利用 Redis、Zookeeper 或数据库来管理),防止不同实例分配到相同的 WorkerID。
4. 总结与对比
| 特性 | UUID | 数据库自增 ID | Redis 生成 | Snowflake |
|---|---|---|---|---|
| 有序性 | 无序 | 有序 | 有序 | 趋势递增 |
| 性能 | 高 | 低 (受限于 DB 写入) | 中 (受限于网络 IO) | 极高 (本地内存生成) |
| 分布式支持 | 支持 (概率不重复) | 难 (需步长模式) | 支持 | 支持 (位运算区分) |
| 存储空间 | 36位字符串,占用大 | 整数,小 | 整数,小 | Long (64bit), 小 |
| 依赖 | 无 | 数据库 | Redis | 服务器时钟 |