一、雪花算法是什么

雪花算法(Snowflake)由 Twitter 开源,生成的 ID 是一个 64 位的长整型数字,结构如下:

  1. 符号位(1 位):固定为 0,确保 ID 为正数。
  2. 时间戳(41 位):记录毫秒级时间,取值为当前时间减去自定义的起始时间,可支撑约 69 年(2^41 / (3652460601000) ≈ 69)。
  3. 机器 ID(10 位):用于区分不同服务器或进程,支持最多 1024 个节点(2^10 = 1024)。
  4. 序列号(12 位):同一毫秒内同一机器生成的 ID 序号,最大支持 4096 个 ID / 毫秒(2^12 = 4096)。

二、雪花算法存在的问题

  1. 时钟回拨问题:若服务器时钟因故障或同步出现回拨,会导致生成的 ID 与历史 ID 重复,这是最核心的风险。
  2. 机器 ID 分配问题:机器 ID 需手动或通过第三方服务分配,若分配重复,不同机器会生成相同 ID;若节点数超过 1024,现有结构无法支撑。
  3. ID 无序性:同一毫秒内的 ID 按序列号递增,但跨毫秒时可能因系统延迟导致后生成的 ID 数值小于先生成的 ID,不满足严格递增需求。
  4. 依赖系统时间:算法完全依赖服务器本地时间,时间同步异常会直接影响 ID 生成的准确性。
  5. 如果并发量很小,用雪花算法生成的id最后几位都是0,就会导致分库分表时候分到同一个表,导致数据倾斜,这个有什么已知的好的解决办法?

三、问题的解决方法

1. 解决时钟回拨
  • 记录最近一次生成 ID 的时间戳,若检测到当前时间小于该时间戳,判定为时钟回拨。
  • 回拨时间较短时(如 5ms 内),可阻塞等待时间追平后再生成 ID;回拨时间较长时,直接抛出异常,避免生成重复 ID。
2. 解决机器 ID 分配
  • 引入分布式协调服务(如 ZooKeeper、Etcd),节点启动时自动从服务中申请唯一机器 ID,避免手动分配错误。
  • 若需支持更多节点,可调整算法结构,例如减少时间戳位数、增加机器 ID 位数(如 12 位机器 ID 可支持 4096 个节点)。
3. 解决 ID 无序性
  • 对生成的 ID 进行二次排序,在存储或使用前按时间戳 + 序列号排序,确保外部使用时的有序性。
  • 改用 “时间戳 + 递增序号” 的全局发号器模式(如 Redis 自增),牺牲部分性能换取严格递增。
4. 解决时间依赖
  • 部署 NTP 时间同步服务,确保所有节点时间偏差控制在毫秒级以内。
  • 引入 “逻辑时间”,用节点内递增的计数器替代物理时间,避免物理时钟波动的影响。
5. 如果并发量很小,用雪花算法生成的id最后几位都是0,就会导致分库分表时候分到同一个表,导致数据倾斜,这个有什么已知的好的解决办法?
A、改造雪花算法:主动增加序列号随机性

既然并发低时序列号总是 0,可通过主动 “扰动” 序列号来避免固定值,同时不破坏 ID 的唯一性和大致有序性。

  1. 序列号随机偏移

    • 当同一毫秒内生成的 ID 数量较少(如≤10)时,不使用从 0 开始的递增序列号,而是在[0, 4095]范围内随机生成一个序列号(确保同一毫秒内不重复即可)。
    • 例如:并发低时,序列号不再是0,1,2...,而是123, 456, 789...(同一毫秒内去重)。
    • 优点:改动小,仅调整序列号生成逻辑,不影响 ID 整体结构和有序性。
  2. 时间戳 + 随机数拼接(弱化序列号)

    • 若对 ID 的严格有序性要求不高(仅需大致按时间递增),可减少序列号位数(如从 12 位减到 4 位),腾出的位数分配给一个 “随机扰动位”(如 8 位)。
    • 结构变为:符号位(1) + 时间戳(41) + 机器ID(10) + 随机扰动位(8) + 序列号(4)。
    • 即使序列号为 0,随机扰动位也能保证 ID 后缀的多样性,避免分表倾斜。
B、分表路由优化:不直接依赖 ID 的原始值

绕过 ID 本身的固定后缀问题,通过路由算法的调整实现数据均匀分布。

  1. ID 哈希后再取模

    • 不直接用 ID 原值取模分表,而是先对 ID 进行哈希计算(如 MD5、SHA1 取前几位),再用哈希结果取模。
    • 例如:table_index = hash(id) % table_count。
    • 哈希计算会将固定后缀的 ID 映射为分散的哈希值,即使 ID 后缀为 0,哈希结果也会均匀分布,避免倾斜。
    • 注意:哈希可能破坏 ID 的有序性,若业务需要按 ID 范围查询(如 “查询某时间段的订单”),需结合时间戳单独建索引。
  2. 引入额外随机因子参与路由

    • 分表时不仅用 ID,还结合一个与业务相关的随机因子(如用户 ID 的后几位、设备 ID 的哈希值)共同计算路由。
    • 例如:table_index = (id + user_id) % table_count。
    • 即使 ID 固定,用户 ID 的随机性也能保证路由结果分散,适合有明确关联维度的业务(如用户相关数据)。
C、替换 ID 生成策略:选择更适合低并发场景的算法

若雪花算法的 “有序性” 并非必需,可改用天然具备随机性的 ID 生成方案,从源头避免后缀固定问题。

  1. UUID/GUID

    • UUID 是 128 位的全局唯一标识符,通过随机数、时间戳、机器 MAC 等组合生成,原生具备高度随机性,分表时哈希或取模均可均匀分布。
    • 优点:无需协调,实现简单,低并发场景下性能足够;缺点:ID 较长(字符串或 128 位数字),不具备有序性,可能影响索引性能。
  2. 数据库自增 ID + 随机前缀 / 后缀

    • 用数据库自增 ID 保证唯一性和有序性,生成后在 ID 前 / 后拼接一段随机数(如随机数+自增ID)。
    • 例如:自增 ID 为 1001,拼接随机数 234 后得到2341001,分表时用该值取模,随机性由随机数保证。
    • 优点:兼容自增 ID 的有序性,同时解决固定后缀问题;缺点:依赖数据库,分布式场景需额外处理自增 ID 冲突(如分段自增)。
D、最佳实践建议
  • 优先选 “ID 哈希后取模”:改动最小(无需修改 ID 生成逻辑),仅调整分表路由算法,适合已上线的系统快速修复数据倾斜。
  • 新系统推荐 “改造雪花算法序列号”:在保留雪花算法优势(有序、紧凑、分布式友好)的同时,通过随机序列号从源头避免倾斜,兼顾性能和分布均匀性。

更多推荐