后端与运维
大厂海量订单,全局唯一 ID 是怎么算出来的?雪花算法 Snowflake 详解
订单表用自增 ID,分库分表后直接撞车:
分库 1 的订单表:ID 1, 2, 3 ...
分库 2 的订单表:ID 1, 2, 3 ... ⚠️ 撞车了!
UUID 生成的是 36 位字符串,太长,而且不是递增的,不适合做索引。
大厂几乎都在用同一种方案——雪花算法(Snowflake),Twitter 开源,一行代码生成 64 位唯一 ID。
白话版
雪花算法像给每个人发身份证号:
420106 19900101 0001 888
- 420106 — 户籍所在地(机器节点 ID)
- 19900101 — 出生年月日(时间戳)
- 0001 — 当天出生的流水号(序列号)
- 888 — 校验位(符号位,未用)
不需要联网,不需要中央发号器,每台机器自己就能生成全局唯一的 ID。 而且生成的 ID 是按时间递增的,对数据库索引非常友好。
64 位 Bit 结构详解
雪花算法生成的 ID 是一个 64 位的长整型(Long),由以下 4 部分组成:
0 - 0000000000 0000000000 0000000000 0000000000 0 - 0000000000 - 000000000000
| | | | | | | |
1bit 41bit毫秒时间戳 10bit工作机器ID 12bit序列号
| 字段 | 占用位数 | 说明 |
|---|---|---|
| 1bit 符号位 | 1 bit | 始终为 0,保证生成的 ID 为正数 |
| 41bit 时间戳 | 41 bit | 当前毫秒数减去初始基准时间戳。41 位可以支撑使用 69 年 ((2^41 - 1) / (1000 * 60 * 60 * 24 * 365) ≈ 69.7 年) |
| 10bit 机器标识 | 10 bit | 可以支持 2^10 = 1024 台机器节点(如 5 bit 数据中心 ID + 5 bit 机器 ID) |
| 12bit 序列号 | 12 bit | 同一毫秒内产生的不同 ID 序号,2^12 = 4096,即单节点单毫秒最多生成 4096 个 ID |
理论上单节点吞吐量可达:
4096 * 1000 = 409 万个 ID / 秒!
Python 实现示例
import time
class SnowflakeIdWorker:
def __init__(self, datacenter_id, worker_id, sequence=0):
# 初始基准时间戳 (2026-01-01)
self.twepoch = 1767225600000
self.datacenter_id_bits = 5
self.worker_id_bits = 5
self.sequence_bits = 12
self.max_datacenter_id = -1 ^ (-1 << self.datacenter_id_bits)
self.max_worker_id = -1 ^ (-1 << self.worker_id_bits)
self.sequence_mask = -1 ^ (-1 << self.sequence_bits)
self.worker_id_shift = self.sequence_bits
self.datacenter_id_shift = self.sequence_bits + self.worker_id_bits
self.timestamp_left_shift = self.sequence_bits + self.worker_id_bits + self.datacenter_id_bits
self.datacenter_id = datacenter_id
self.worker_id = worker_id
self.sequence = sequence
self.last_timestamp = -1
def _time_gen(self):
return int(time.time() * 1000)
def _til_next_millis(self, last_timestamp):
timestamp = self._time_gen()
while timestamp <= last_timestamp:
timestamp = self._time_gen()
return timestamp
def get_id(self):
timestamp = self._time_gen()
# 时钟回拨处理
if timestamp < self.last_timestamp:
raise Exception("Clock moved backwards. Refusing to generate id")
if self.last_timestamp == timestamp:
self.sequence = (self.sequence + 1) & self.sequence_mask
if self.sequence == 0:
timestamp = self._til_next_millis(self.last_timestamp)
else:
self.sequence = 0
self.last_timestamp = timestamp
new_id = ((timestamp - self.twepoch) << self.timestamp_left_shift) | \
(self.datacenter_id << self.datacenter_id_shift) | \
(self.worker_id << self.worker_id_shift) | \
self.sequence
return new_id
# 使用示例
worker = SnowflakeIdWorker(datacenter_id=1, worker_id=1)
print("生成的全局唯一ID:", worker.get_id())
雪花算法的核心痛点:时钟回拨
因为雪花算法极其依赖服务器系统的本地时间戳,如果服务器发生了 NTP 时钟同步或手动调整,导致系统时间回拨(时间倒退),就会产生重复的 ID。
常见的解决方案:
- 直接抛出异常:若回拨时间较短(如几毫秒),等待时间追平后再继续生成。
- 使用备用工作节点 ID:当时钟回拨发生时,切换使用保留的 Worker ID 保证唯一性。
- 百度 UidGenerator / 美团 Leaf:基于雪花算法进行了改进,结合 Redis 或 ZooKeeper 保证时间递增与时钟容错。
小结
- 优点:高性能(百万 QPS)、递增索引友好、不依赖中心化数据库。
- 缺点:依赖系统时钟,需防范时钟回拨风险。
关注「善忘技术夹」全媒体矩阵
扫描上方宣传海报二维码,第一时间获取最新技术文章、开源项目与免安装小程序体验。