首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >为轮子造轮子的教训经验场景

为轮子造轮子的教训经验场景

作者头像
bisal
发布2026-03-12 16:34:38
发布2026-03-12 16:34:38
140
举报

点击标题下「蓝色微信名」可快速关注

我们的应用系统设计中可能会用到ID字段,有可能是通过数据库生成的,有可能是应用自己生成的,有可能是无业务含义的,有可能是夹杂着业务属性的,可能不同的场景,有着不同的生成方案。

技术社群的这篇文章《雪花算法ID重复了?惨痛生产教训:请勿轻易造轮子!》给我们讲解了应用设计ID的问题,提到的相关方案,对我们的工作可能有所帮助和指导。

原文如下,

最近线上系统发生了一个事件:订单号/流水号出现了重复,影响了核心业务流程。最终定位到根源:一个自研的二方包雪花算法ID生成器出现了问题。

下面我们来回顾一下雪花算法的标准结构,分析问题出在哪,总结一些通用的设计建议。

一、标准雪花算法(Snowflake)

标准的Snowflake ID由一个64位long型整数构成:

代码语言:javascript
复制
+----------------------------------------------------------------------------------------------------+
| 1 Bit | 41 Bits 时间戳 | 5 Bits 数据中心ID | 5 Bits 机器ID | 12 Bits 序列号 |
+----------------------------------------------------------------------------------------------------+
  • 1位符号位:始终为0,确保生成正数。
  • 41位时间戳:记录与固定起始时间的毫秒差,可支持约69年。
  • 10位机器ID:用于标识不同节点。
  • 12位序列号:在同一毫秒内生成多个ID时使用,最多支持每毫秒生成4096个ID。

优点:

  • 高性能生成唯一ID,按时间有序,适用于分布式环境。

二、我们的“定制版”雪花算法:问题在哪?

我们使用的二方包雪花算法结构如下(根据排查推测):

代码语言:javascript
复制
+----------------------------------------------------------------------------------------------------+
| 31 Bits 时间戳Delta | 13 Bits 数据中心ID | 4 Bits 工作ID | 8 Bits 业务ID | 8 Bits 序列号 |
+----------------------------------------------------------------------------------------------------+

看起来字段丰富,但存在严重问题:

1、时间戳仅保留31位,最多支持24.85天!

  • 左移33位后只用31位时间戳,
  • 超过2312^{31}231毫秒后开始循环,
  • 我们自定义的起始时间是2018年,2025年时早已绕了无数圈。

2、BusinessId用的是IP最后一段

  • 使用的IP用点分隔的最后一位,即192.168.0.1的1,极容易重复。

3、WorkId和DataCenterId未配置,全为0

  • 相当于所有实例共享同一节点标识,唯一性形同虚设。

最终结果:时间轮回 + IP冲突 + 序列重复,ID彻底撞车。

三、教训总结

通用组件不建议自研

  • 雪花算法涉及时钟回拨、位运算、分布式协调等关键细节,成熟组件更稳妥。

不盲信二方包

  • 无论谁写的代码,都要看清实现逻辑,理解其唯一性保障机制。

合理设置机器ID

  • 靠IP后缀太脆弱,建议集中规划,统一分配Worker ID和DataCenter ID。

提前覆盖边界场景

  • 模拟长时间运行、序列号溢出、时间回拨等极端情况,确保系统稳健。

四、推荐做法

使用成熟的开源实现,如Hutool、Baomidou等:

代码语言:javascript
复制
// Hutool 示例
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
long id = snowflake.nextId();

// Baomidou 示例(支持从 IP/MAC 自动推导,也可手动指定)
DefaultIdentifierGenerator generator = new DefaultIdentifierGenerator(1, 1); // workerId=1, dataCenterId=1
long id = generator.nextId("user");

对于中大型系统,DataCenterId一般用来标识不同的机房或者AZ (Availability Zone)。

WorkerId的配置策略可以根据系统规模逐步演进:

  • 简单方式:通过配置文件手动指定。这种方式配置简便,适用于开发环境或单机部署。
  • 标准方式:将IP与端口号(或进程号)拼接后进行哈希,再对WorkerId总数取模。具备一定自动化能力,不依赖外部系统,适用于中小规模部署。
  • 中级方案:依赖注册中心(如Eureka、Nacos),在服务注册时分配编号,结合服务ID保障唯一性。
  • 高级方案:使用Redis、Zookeeper等集中协调WorkerId分配与释放,支持动态扩容、避免冲突。

随着系统规模扩大,推荐逐步引入更复杂但更稳妥的机制,避免一开始就过度设计。

五、其它建议:不要将业务标志拼入ID中

有时我们为了确保唯一性,会试图将业务信息(如类型前缀、模块编号)拼接进ID。但这种做法会带来一系列问题:

  • 会导致 ID非全数字,失去原本按时间递增的排序特性,影响数据库索引效率;
  • ID长度变得不规则或偏长,可能增加存储成本,也会影响日志展示、用户体验;
  • 若业务字段含义变动,还可能造成数据兼容性问题。

更稳妥的做法是将业务字段单独存储,ID仅用于唯一标识和排序。

六、结语

别为造轮子而造轮子,尤其在通用组件上,不可抱侥幸心理。

如果您认为这篇文章有些帮助,还请不吝点下文章末尾的"点赞"和"在看",或者直接转发朋友圈

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-01-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 bisal的个人杂货铺 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档