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

某天线上日志偶尔报错: “主键重复” 。一个看似简单的业务场景——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  
publicclassMybatisPlusConfig{  

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

    // 核心逻辑:优先从环境变量获取,否则IP计算  
    privatelonggetWorkerId(){  
        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等方案实现多级容灾?

Logo

更多推荐