善忘技术夹 Logo
善忘技术夹
后端与运维

大厂海量订单,全局唯一 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。

常见的解决方案:

  1. 直接抛出异常:若回拨时间较短(如几毫秒),等待时间追平后再继续生成。
  2. 使用备用工作节点 ID:当时钟回拨发生时,切换使用保留的 Worker ID 保证唯一性。
  3. 百度 UidGenerator / 美团 Leaf:基于雪花算法进行了改进,结合 Redis 或 ZooKeeper 保证时间递增与时钟容错。

小结

  • 优点:高性能(百万 QPS)、递增索引友好、不依赖中心化数据库。
  • 缺点:依赖系统时钟,需防范时钟回拨风险。

关注「善忘技术夹」全媒体矩阵

扫描上方宣传海报二维码,第一时间获取最新技术文章、开源项目与免安装小程序体验。

善忘技术夹 微信公众号宣传图
微信公众号 (扫码关注)
善忘技术夹 微信小程序宣传图
微信小程序 (扫码即用)