这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
序列号
序列号用于在同一毫秒内区分不同的 ID。
时间戳的精度是毫秒,而同一毫秒里可能涌进成千上万个请求。序列号就是这段时间里用来区分它们的位:毫秒没变就靠序列号继续往后排,毫秒一变就进入新的一段序列号区间。所以序列号解决的其实是一个并发问题——它是”毫秒内的计数器”,不是全局计数器。
12 位序列号意味着同一毫秒内最多 4096 个 ID,理论上每秒可以发出 4096 × 1000 ≈ 409 万个。听起来很多,但这只是单节点的量级:一旦同一毫秒内的请求数超过 4096,序列号就耗尽了,生成逻辑只能等待下一个毫秒,延迟会在那一毫秒上堆积起来。
读完这一章,你应该能按单节点峰值 QPS 算出需要多少位序列号,并解释序列号耗尽时”等待下一毫秒”会怎样改变延迟曲线,以及为什么序列号位数和机器 ID 位数必须一起权衡。
容量
12 位序列号 = 2^12 = 4,096 个 ID/毫秒
QPS 计算
每毫秒 4,096 个 ID
每秒 4,096 × 1,000 = 4,096,000 个 ID
理论 QPS:> 4,000,000
原理
同一毫秒内,序列号从 0 递增到 4095。
实现
序列号的递增逻辑分三种情况:时间戳比上一次大,说明进入了新的一毫秒,序列号直接归零;时间戳和上一次相同,序列号加一;时间戳比上一次小,就是时钟回拨,应当拒绝发号。
1. ts = 当前毫秒时间戳
2. 若 ts < lastTimestamp → 时钟回拨,拒绝发号
3. 若 ts == lastTimestamp → seq = (seq + 1) & 0xFFF
seq 回绕为 0 说明本毫秒已发满 → 等待下一毫秒,ts 前进
4. 若 ts > lastTimestamp → seq = 0
5. 组装:ID = (ts << 22) | (machineId << 12) | seq
多线程并发时,序列号的自增必须保证原子性,通常用 synchronized 或 CAS 实现,临界区里只有几次位运算,对性能的影响可以忽略。41 位时间戳、10 位机器 ID、12 位序列号加上 1 位符号位,正好凑满 64 位。
了解了序列号后,我们学习时钟回拨问题及解决方案。
👉 下一节:时钟回拨
发现内容有问题?
告诉我们哪里需要改进。
