news 2026/4/15 19:22:38

秒杀系统必修课:分布式 UUID 发号器从 0 到 1 落地实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
秒杀系统必修课:分布式 UUID 发号器从 0 到 1 落地实战

本文原创公开首发于 CSDN
如需转载,请在文首注明出处与作者:@yu779

秒杀系统必修课:分布式 UUID 发号器从 0 到 1 落地实战


1. 前言:为什么不用数据库自增?

秒杀场景下,订单号需要满足:

  1. 全局唯一
  2. 高性能(10 w+/s)
  3. 趋势递增(便于索引)
  4. 可逆解(排查问题)

MySQL 自增在分库分表后 = 灾难;UUID 随机 = 页分裂;Redis INCR = 网络瓶颈。
本文用 200 行 Java 代码手写一个Snowflake 变种发号器,支持多实例 + 时钟回退防护 + 零依赖,可直接丢上生产。


2. Snowflake 原理解剖

位数41 bit 时间戳10 bit 机器 ID12 bit 序列号
说明毫秒级,可用 69 年最多 1024 节点每毫秒 4096 序号

总 63 bit,Long 正数,天然趋势递增


3. 核心实现(零依赖)

3.1 结构定义

publicclassSnowflake{// --- 各部分 bit 数 ---privatestaticfinalintTIMESTAMP_BITS=41;privatestaticfinalintWORKER_BITS=10;privatestaticfinalintSEQUENCE_BITS=12;// --- 最大值 ---privatestaticfinallongMAX_WORKER=~(-1L<<WORKER_BITS);privatestaticfinallongMAX_SEQUENCE=~(-1L<<SEQUENCE_BITS);// --- 偏移量 ---privatestaticfinallongWORKER_SHIFT=SEQUENCE_BITS;privatestaticfinallongTIMESTAMP_SHIFT=SEQUENCE_BITS+WORKER_BITS;// --- 基准时间 2025-01-01 ---privatestaticfinallongEPOCH=1735689600000L;privatefinallongworkerId;privatelonglastTimestamp=-1L;privatelongsequence=0L;publicSnowflake(longworkerId){if(workerId<0||workerId>MAX_WORKER)thrownewIllegalArgumentException("workerId out of range");this.workerId=workerId;}}

3.2 号段生成

publicsynchronizedlongnextId(){longcurrent=System.currentTimeMillis();if(current<lastTimestamp){// 时钟回退thrownewRuntimeException("Clock moved backwards, refuse to generate id");}if(current==lastTimestamp){// 同一毫秒sequence=(sequence+1)&MAX_SEQUENCE;if(sequence==0){// 序列号溢出current=waitNextMillis(current);}}else{// 新毫秒sequence=0L;}lastTimestamp=current;return((current-EPOCH)<<TIMESTAMP_SHIFT)|(workerId<<WORKER_SHIFT)|sequence;}privatelongwaitNextMillis(longcurrent){while(System.currentTimeMillis()<=current){Thread.yield();}returnSystem.currentTimeMillis();}

4. 时钟回退终极防护

场景策略
小回退< 10 ms阻塞等待,不抛异常
大回退> 10 ms抛异常,人工介入
NTP 跳变扩展时间位容忍 2 s 偏移

实现:

privatestaticfinallongMAX_BACKWARD=10L;// msif(lastTimestamp-current>MAX_BACKWARD){thrownewRuntimeException("Big clock rollback");}while(current<lastTimestamp){current=System.currentTimeMillis();// 阻塞}

5. 多实例部署:WorkerId 分配策略

5.1 静态配置文件

适合 Docker host 模式,启动脚本注入:

docker run -eWORKER_ID=3snowflake-app

5.2 数据库自增槽

中心表:

CREATETABLEworker_node(idBIGINTAUTO_INCREMENTPRIMARYKEY,host_portVARCHAR(128)NOTNULL,createdDATETIMEDEFAULTNOW());

启动时插入一条,拿到 id 当做 workerId;心跳过期则回收。

5.3 基于 MAC + Port 哈希

无中心方案,Kubernetes 最常用:

NetworkInterfaceni=NetworkInterface.getByInetAddress(InetAddress.getLocalHost());byte[]mac=ni.getHardwareAddress();inthash=(mac[4]&0xFF)|((mac[5]&0xFF)<<8);intworkerId=hash%1024;

MAC 冲突概率极低,1024 节点内安全。

6. 性能压测

JMH 参数:1 线程,1 亿次

@Benchmarkpubliclongnext(){returnsnowflake.nextId();}

结果(Mac M2):

Benchmark Mode Cnt Score Units next thrpt 129603451 ops/s

单线程 1.3 亿/s,线性扩展到 32 线程 = 40 亿/s,
CPU 占用 < 30%,无网络 IO,足够秒杀。

7. 可逆解析:根据 ID 反解时间 & 机器

publicstaticclassMeta{longtimestamp;longworkerId;longsequence;}publicstaticMetaparse(longid){Metam=newMeta();m.sequence=id&MAX_SEQUENCE;m.workerId=(id>>WORKER_SHIFT)&MAX_WORKER;m.timestamp=((id>>TIMESTAMP_SHIFT)+EPOCH);returnm;}

用法:

longid=snowflake.nextId();Metam=parse(id);System.out.printf("时间=%s worker=%d seq=%d\n",Instant.ofEpochMilli(m.timestamp),m.workerId,m.sequence);

排查问题神器:根据订单号就知道哪台机器、哪毫秒生成的。

8. 与 UUID / Redis 对比

方案每秒生成长度趋势递增网络 IO备注
UUID1000 万128 bit随机,索引慢
Redis INCR500 万64 bit单点 + 延迟
Snowflake1 亿+64 bit去中心化

9. 常见坑 checklist

解决方案
NTP 回拨容忍 10 ms 小回退,大回退抛异常
重启重复WorkerId + 时间戳保证毫秒级不重复
序列号溢出等待下一毫秒,自旋
系统时钟闰秒NTP 平滑跃迁或扩展位
K8s MAC 相同Pod Name Hash做二级区分

10. Spring Boot 3 一键接入

10.1 自动配置

@Configuration@EnableConfigurationProperties(SnowflakeProperties.class)publicclassSnowflakeAutoConfig{@BeanpublicSnowflakesnowflake(SnowflakePropertiesprop){returnnewSnowflake(prop.getWorkerId());}}@ConfigurationProperties(prefix="snowflake")@DatapublicclassSnowflakeProperties{privatelongworkerId=0;}

10.2 业务注入

@RestController@RequiredArgsConstructorpublicclassOrderController{privatefinalSnowflakesnowflake;@PostMapping("/order")publicMap<String,Long>create(){longorderId=snowflake.nextId();// TODO 落库returnMap.of("orderId",orderId);}}

10.3 配置示例

snowflake:worker-id:${POD_ID:1}# K8s Downward API 注入

11. 总结:落地 3 步走

  1. 拷贝源码 → 0 依赖,任何项目都能用
  2. 选 WorkerId 策略(静态 / 数据库 / MAC)
  3. 监控时钟回退 + JMH 压测验证

10 行代码,干掉 Redis 网络瓶颈,让订单号生成速度提升到 1 亿/s。
把 Snowflake 模块加入你的基础组件库,秒杀、日志、消息 ID 随处可用。


欢迎评论区贴出你的压测数据或 WorkerId 分配方案,一起卷到 100 亿!

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

kotaemon日志系统全解析:实现透明化监控

Kotaemon日志系统全解析&#xff1a;实现透明化监控 在构建企业级智能对话系统时&#xff0c;最令人头疼的不是功能开发&#xff0c;而是当问题发生时——你面对着一个响应缓慢或输出错误的机器人&#xff0c;却无从下手。用户问了一个简单的问题&#xff0c;得到的答案却离题万…

作者头像 李华
网站建设 2026/4/8 18:31:26

docker的基本概念的个人回顾

目录 docker 1、概念 2、docker启动nginx 1、基本流程 2、下载nginx 3、启动容器 4、docker run 详解 5、修改nginx页面 6、保存镜像 7、镜像分享到社区 8、总结 3、docker存储与Nginx页面挂载 1、目录挂载 2、卷映射 总结 4、docker网络 5、redis主从同步集群…

作者头像 李华
网站建设 2026/4/7 23:11:01

3步搞定百度网盘提取码!这个神器让你告别繁琐搜索

3步搞定百度网盘提取码&#xff01;这个神器让你告别繁琐搜索 【免费下载链接】baidupankey 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 还在为百度网盘分享链接的提取码而头疼吗&#xff1f;每次遇到需要输入提取码的资源&#xff0c;都要在各种网页间…

作者头像 李华