news 2026/9/1 6:09:27

雪花算法(Snowflake Algorithm)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
雪花算法(Snowflake Algorithm)

雪花算法(Snowflake Algorithm)

一篇带你搞懂雪花算法的文章,从原理到实现,从问题到优化。


目录

  1. 什么是雪花算法
  2. 算法原理详解
  3. 核心特点与优势
  4. Java 完整实现
  5. 常见问题与解决方案
  6. 与其他 ID 方案对比
  7. 生产环境最佳实践
  8. 总结

1. 什么是雪花算法

雪花算法(Snowflake Algorithm)是 Twitter 开源的一种分布式 ID 生成算法,用于在分布式系统中生成全局唯一趋势递增的 64 位 ID。

诞生背景

在分布式系统中,传统数据库自增 ID 无法满足需求,UUID 虽然唯一但无序且占用空间大。雪花算法应运而生,既保证了 ID 的唯一性,又具备良好的排序性和高性能。

一句话概括

雪花算法 = 分布式系统里的"身份证号码生成器"


2. 算法原理详解

2.1 64 位 ID 结构

雪花算法生成的 ID 是一个long类型的 64 位整数,结构如下:

字段位数说明
符号位1 位固定为 0,保证 ID 为正数
时间戳41 位毫秒级时间,可支持 69 年
机器 ID10 位最多支持 1024 个节点
序列号12 位同一毫秒内可生成 4096 个 ID

2.2 生成规则

  • 同一毫秒内:序列号递增(0 → 1 → 2 → … → 4095)
  • 序列号用完:等待下一毫秒,序列号重置为 0
  • 跨毫秒:序列号重置为 0,时间戳更新
  • 不同机器:通过机器 ID 区分

2.3 ID 拼装过程

longid=((timestamp-twepoch)<<timestampShift)// 时间戳左移22位|(workerId<<workerIdShift)// 机器ID左移12位|sequence;// 序列号占低12位

3. 核心特点与优势

特点说明
全局唯一时间戳 + 机器 ID + 序列号组合保证唯一,机器 ID 不冲突则绝不重复
趋势递增包含时间戳,ID 按时间有序,对 B+ 树索引友好,插入性能高
高性能纯内存计算,无 IO 开销,单机 QPS 可达数百万
高可用不依赖外部系统(数据库、Redis 等),每个节点独立生成
灵活可定制可根据业务调整位数分配,如增加机器 ID 位数支持更多节点

4. Java 完整实现

4.1 核心代码

packagecom.example.snowflake;/** * 雪花算法ID生成器 */publicclassSnowflakeIdWorker{// ============================== 基础配置 ==============================/** 开始时间截 (2026-08-08 00:00:00) */privatefinallongtwepoch=1783584000000L;/** 机器ID所占位数 */privatefinallongworkerIdBits=10L;/** 序列号所占位数 */privatefinallongsequenceBits=12L;/** 机器ID最大值 1023 */privatefinallongmaxWorkerId=~(-1L<<workerIdBits);/** 序列号最大值 4095 */privatefinallongsequenceMask=~(-1L<<sequenceBits);/** 机器ID左移位 */privatefinallongworkerIdShift=sequenceBits;/** 时间戳左移位 */privatefinallongtimestampShift=workerIdBits+sequenceBits;// ============================== 工作状态 ==============================privatefinallongworkerId;privatelongsequence=0L;privatelonglastTimestamp=-1L;publicSnowflakeIdWorker(longworkerId){if(workerId>maxWorkerId||workerId<0){thrownewIllegalArgumentException(String.format("workerId应在 0 ~ %d 之间",maxWorkerId));}this.workerId=workerId;}/** * 生成下一个ID(线程安全) */publicsynchronizedlongnextId(){longtimestamp=timeGen();// 时钟回拨处理if(timestamp<lastTimestamp){longoffset=lastTimestamp-timestamp;if(offset<=5){// 回拨5ms内,等待时间追上try{wait(offset);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}timestamp=timeGen();}else{thrownewRuntimeException(String.format("时钟回拨超过5ms,拒绝生成ID。回拨: %dms",offset));}}// 同一毫秒内生成if(lastTimestamp==timestamp){sequence=(sequence+1)&sequenceMask;if(sequence==0){timestamp=tilNextMillis(lastTimestamp);}}else{sequence=0L;}lastTimestamp=timestamp;return((timestamp-twepoch)<<timestampShift)|(workerId<<workerIdShift)|sequence;}privatelongtilNextMillis(longlastTimestamp){longtimestamp=timeGen();while(timestamp<=lastTimestamp){timestamp=timeGen();}returntimestamp;}privatelongtimeGen(){returnSystem.currentTimeMillis();}/** * 解析ID(调试用) * 注意:使用 >>> 无符号右移,避免符号扩展问题 */publicStringparseId(longid){longtimestamp=(id>>>timestampShift)+twepoch;longworkerId=(id>>>workerIdShift)&maxWorkerId;longsequence=id&sequenceMask;returnString.format("ID: %d | 时间: %s | 机器ID: %d | 序列号: %d",id,newjava.util.Date(timestamp),workerId,sequence);}}

4.2 使用示例与运行结果

publicclassSnowflakeDemo{publicstaticvoidmain(String[]args){SnowflakeIdWorkerworker=newSnowflakeIdWorker(1);for(inti=0;i<10;i++){longid=worker.nextId();System.out.println(worker.parseId(id));}}}

运行结果:

ID: 733946724203208704 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 0 ID: 733946724203208705 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 1 ID: 733946724203208706 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 2 ID: 733946724203208707 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 3 ID: 733946724203208708 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 4 ID: 733946724203208709 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 5 ID: 733946724203208710 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 6 ID: 733946724203208711 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 7 ID: 733946724203208712 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 8 ID: 733946724203208713 | 时间: Tue Aug 18 14:23:57 CST 2026 | 机器ID: 1 | 序列号: 9

从运行结果可以看出:

  • 同一毫秒内生成的 ID,序列号从 0 开始依次递增
  • 所有 ID 均为正数(符号位为 0)
  • 机器 ID 固定为 1,与构造时传入的参数一致

5. 常见问题与解决方案

5.1 时钟回拨(Clock Drift)

问题:系统时间被回拨(NTP 同步或手动修改),可能导致 ID 重复。

解决方案

方案说明适用场景
等待策略检测到回拨后等待时间追上回拨时间短(< 5ms)
抛异常回拨超过阈值直接抛出异常回拨时间长,需人工介入
备用方案使用其他 ID 生成方式降级高可用要求严格
ZooKeeper 协调将机器 ID 等信息存入 ZK,动态分配大规模分布式系统

代码实现见第 4 节nextId()中的回拨处理逻辑。

5.2 机器 ID 怎么分配?

方式说明优缺点
手动配置配置文件指定简单,但易出错
ZooKeeper节点注册时分配自动管理,高可用
Redis用 Redis 自增分配实现简单
IP / 主机名根据 IP 计算无需外部依赖,但可能冲突

6. 与其他 ID 方案对比

特性雪花算法UUID数据库自增 IDRedis 自增
唯一性全局唯一全局唯一仅单库唯一全局唯一
有序性趋势递增无序递增递增
性能极高(~200 万 QPS)低(依赖 DB)
空间占用8 字节(Long)36 字节(String)8 字节8 字节
依赖外部无依赖无依赖依赖数据库依赖 Redis
分布式原生支持支持需额外处理支持

为什么不用 UUID?

  • 无序:作为数据库主键会导致 B+ 树索引页分裂,性能下降
  • 存储大:36 字节字符串,是雪花算法的 4.5 倍
  • 可读性差:无任何业务含义

7. 生产环境最佳实践

7.1 机器 ID 管理(推荐 ZooKeeper)

// 伪代码示例publicclassWorkerIdManager{publiclonggetWorkerId(){// 1. 连接 ZooKeeper// 2. 在 /snowflake/workers 下创建顺序节点// 3. 返回节点序号作为 workerId// 4. 监听节点变化,处理机器 ID 回收}}

7.2 时钟同步

  • 使用 NTP 定期同步系统时间
  • 监控系统时间,发现异常及时告警
  • 回拨策略根据业务容忍度设置阈值(建议 5ms)

7.3 参数调优建议

业务场景建议配置说明
普通业务标准配置(10 位机器 ID + 12 位序列号)足够覆盖绝大多数场景
超大规模增加机器 ID 位数(如 12 位)支持更多节点
高并发增加序列号位数(如 14 位)同一毫秒支持更多 ID
长期项目增加时间戳位数延长算法可用年限

8. 总结

核心要点

  • 64 位 ID 结构:1 位符号位 + 41 位时间戳 + 10 位机器 ID + 12 位序列号
  • 全局唯一:时间戳 + 机器 ID + 序列号三重保证
  • 趋势递增:对数据库索引友好,插入性能高
  • 高性能:纯内存位运算,QPS 可达百万级
  • 时钟回拨:需妥善处理,常用等待或抛异常策略

适用场景

推荐使用:

  • 分布式系统主键 ID
  • 订单号、流水号
  • 消息队列消息 ID
  • 日志跟踪 ID

不推荐使用:

  • 需要严格连续递增(如发票号)
  • 需要包含业务含义(可用前缀 + 雪花 ID)

一句话总结

雪花算法是分布式系统中最优雅的 ID 生成方案:高性能、全局唯一、趋势递增,用 64 位整数解决了分布式 ID 生成的所有核心痛点。


希望这篇文章对你有所帮助。有任何问题也欢迎在评论区交流讨论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 6:09:02

CRT特丽珑显示器重温经典动画:技术原理与实战配置指南

在数字内容消费日益高清化、高刷化的今天&#xff0c;我们习惯于用4K OLED屏幕观看最新的影视作品。然而&#xff0c;有一种独特的体验正在被一小部分爱好者重新发掘&#xff1a;使用上世纪末至本世纪初的CRT显示器&#xff0c;特别是索尼特丽珑&#xff08;Trinitron&#xff…

作者头像 李华
网站建设 2026/9/1 6:08:14

LAMMPS源码编译与GPU加速全流程指南

简介&#xff1a;一份面向 Windows 平台上 Lammps 用户的开源源码资源&#xff0c;定位是解决从安装、环境配置到 GPU 加速调用的实操问题。包体共 3 个文件&#xff0c;以 HTML 说明页为主体&#xff0c;配以 .inscode 工程入口和 .gitignore 工程配置&#xff0c;整体约 5KB&…

作者头像 李华
网站建设 2026/9/1 6:07:56

收费合理的小程序制作公司怎么选:新手创业和夫妻店先看这几项

新手创业、夫妻店和社区小门店做小程序&#xff0c;最容易被“低价”带偏。有人只看几百元的开通费用&#xff0c;忽略了支付、商品管理和后续维护&#xff1b;也有人一开始就按定制项目报价&#xff0c;功能还没想清楚&#xff0c;预算已经先超出预期。对这类商家来说&#xf…

作者头像 李华
网站建设 2026/9/1 6:07:13

DeepSeek Harness插件开发指南:提示词管理与API调用增强

这次我们来看一个为 DeepSeek Harness 开发的插件项目。DeepSeek Harness 本身是一个功能强大的 AI 编程助手&#xff0c;但官方功能总有覆盖不到的地方。这个项目就是针对官方缺失的两个实用功能&#xff0c;开发了对应的插件来补全。对于经常使用 DeepSeek Harness 进行代码生…

作者头像 李华
网站建设 2026/9/1 6:05:35

产线串码写入与校验工具包:从SN到CRC的防呆设计

简介&#xff1a;面向创维电视及智能终端生产线的串码写入与校验工具包&#xff0c;专注解决SN码、MAC地址等关键参数的批量写入、读取与校验&#xff0c;同时集成条码打印与工位检测能力&#xff0c;适用于工厂测试人员、产线信息化工程师以及自动化设备集成商&#xff0c;尤其…

作者头像 李华
网站建设 2026/9/1 6:04:49

状态机与事件驱动:嵌入式软件架构设计的核心实践

嵌入式软件设计架构里面&#xff0c;状态机&#xff08;State Machine&#xff09;和 event 模块经常被放在一起讨论。处理按键、菜单、通信握手、设备上电时序这类任务时&#xff0c;如果业务逻辑全部堆在主循环里&#xff0c;代码结构会随着分支数量增加迅速失控。状态机负责…

作者头像 李华