⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7477530907543257126 「椒盐可乐」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

背景:一次离奇的主键重复事故

某天线上日志偶尔报错: “主键重复” 。一个看似简单的业务场景——APP持续上传信息,用户量不足1W,并发量极低,仅涉及单表插入操作。

然而,就是这个简单的insert,却让团队排查到怀疑人生……

问题定位:

项目基于SpringCloud+MybatisPlus,主键默认使用雪花ID(Snowflake)。排查发现,生产环境部署了分布式集群(A/B/C多台机器),但未配置workId。

最终结论:多台机器因workId相同,导致生成的雪花ID发生碰撞。

知识卡:什么是雪花ID?

核心原理

Snowflake是Twitter开源的分布式ID生成算法,生成64位Long型ID,结构如下:

> 0 | 时间戳(41位) | 数据中心ID(5位) | 机器ID(5位) | 序列号(12位)
>

优点

  • 高性能: 单机每秒可生成26万+ ID。
  • 趋势递增: 整体有序,适合数据库索引。
  • 去中心化: 无需依赖Redis/Zookeeper,本地生成。

致命缺点

  • 时钟回拨: 服务器时间倒退会导致ID重复。
  • 机器ID冲突: 分布式环境下,若workId重复,ID必然重复。
  • ID不连续: 高并发时可能出现“跳号”。

避坑指南:如何确保workId全局唯一?

方案1:IP动态计算(推荐)

利用服务器IP最后一段取模,自动分配workId:

// 示例代码:根据IP生成workId  
String hostAddress = InetAddress.getLocalHost().getHostAddress();
int ipLastSegment = Integer.parseInt(hostAddress.split("\.")[3]);
return ipLastSegment % 32; // 确保workId在0-31范围内
  • 优点: 无需人工干预,IP天然唯一。
  • 注意: 需确保IP末段不重复,适用于静态IP环境。

方案2:环境变量注入(Docker友好)

通过启动命令注入workId:

# Docker启动示例  
docker run -e WORKER_ID=2 -e DATACENTER_ID=1 your-service-image

适用场景: 容器化部署,灵活可控。

方案3:中间件托管(高可用场景)

使用Redis或配置中心(如Nacos)维护workId映射表:

Key: service-name@ip → Value: workId

优点: 适合动态扩缩容,避免IP变化引发问题。

实战代码:MybatisPlus动态配置workId

@Configuration  
publicclass MybatisPlusConfig {

@Bean
public IdentifierGenerator identifierGenerator() {
returnnew DefaultIdentifierGenerator(getWorkerId(), getDatacenterId());
}

// 核心逻辑:优先从环境变量获取,否则IP计算
private long getWorkerId() {
try {
String workerIdStr = System.getenv("WORKER_ID");
if (workerIdStr != null) return Long.parseLong(workerIdStr);

String hostAddress = InetAddress.getLocalHost().getHostAddress();
int ipLastSegment = Integer.parseInt(hostAddress.split("\\.")[3]);
return ipLastSegment % 32;
} catch (Exception e) {
log.error("Get workId failed, fallback to default 1");
return1L; // 兜底策略
}
}

// 数据中心ID同理(略)
}

代码要点:

  • 环境变量优先级 > IP计算 > 默认值兜底。
  • 日志告警:IP获取失败时需人工介入。

总结:分布式ID设计的核心原则

  • 全局唯一: 确保workId在集群内绝对不重复。
  • 容错机制: 时钟回拨、IP变更等场景需有兜底策略。
  • 可观测性: 关键节点增加日志监控,如workId生成过程。

延伸思考:

若服务规模超32台(5位workId上限),如何扩展?

如何结合Leaf、UUID等方案实现多级容灾?

文章目录
  1. 1. 背景:一次离奇的主键重复事故
    1. 1.0.1. 问题定位:
  • 2. 知识卡:什么是雪花ID?
    1. 2.0.1. 核心原理
    2. 2.0.2. 优点
    3. 2.0.3. 致命缺点
  • 3. 避坑指南:如何确保workId全局唯一?
    1. 3.0.1. 方案1:IP动态计算(推荐)
    2. 3.0.2. 方案2:环境变量注入(Docker友好)
    3. 3.0.3. 方案3:中间件托管(高可用场景)
  • 4. 实战代码:MybatisPlus动态配置workId
    1. 4.0.1. 代码要点:
  • 5. 总结:分布式ID设计的核心原则
    1. 5.0.1. 延伸思考: