当前位置: 首页 > news >正文

分库分表下的 ID 冲突问题与雪花算法讲解

大家好,我是工藤学编程 🦉一个正在努力学习的小博主,期待你的关注
实战代码系列最新文章😉C++实现图书管理系统(Qt C++ GUI界面版)
SpringBoot实战系列🐷【SpringBoot实战系列】Sharding-Jdbc实现分库分表到分布式ID生成器Snowflake自定义wrokId实战
环境搭建大集合环境搭建大集合(持续更新)
分库分表分库分表技术栈讲解-Sharding-JDBC

前情摘要:

1、数据库性能优化
2、分库分表之优缺点分析
3、分库分表之数据库分片分类
4、分库分表之策略
5、分库分表技术栈讲解-Sharding-JDBC

本文章目录

    • (一) 分库分表下的ID冲突问题与分布式ID生成方案
      • 分布式ID生成方案对比
        • 1. 数据库自增ID(改进版)
        • 2. UUID(通用唯一识别码)
        • 3. Redis发号器
        • 4. Snowflake雪花算法
        • 数据库号段模式(Leaf-Segment)
        • 美团Leaf方案
      • 方案选型建议
    • (二)雪花算法(Snowflake)详解
        • 一、雪花算法的本质与起源
        • 二、64位ID的结构拆解
        • 三、Java实现示例(含时钟回拨处理)
        • 四、雪花算法的核心坑点与解决方案
          • 坑一:分布式环境下workId重复
          • 坑二:系统时钟回拨导致ID重复
        • 五、雪花算法的适用场景
        • 六、与其他ID方案的对比
      • 总结

前言:在进行实操之前,我们还需要走最后一步,那就是了解分库分表下的 ID 冲突问题

(一) 分库分表下的ID冲突问题与分布式ID生成方案

传统自增ID的局限性

  • 单库环境:MySQL通过AUTO_INCREMENT自动生成唯一主键
  • 分库分表后:不同分片的自增ID会重复(如库1的订单表ID为1,库2的订单表ID也可能为1)

冲突示例

-- 分库前:单库自增ID保证唯一性
INSERT INTO orders(id, user_id) VALUES(NULL, 1001); -- 自动生成ID=1-- 分库后:库1和库2各自生成ID=11: INSERT INTO orders(id, user_id) VALUES(NULL, 1001); -- ID=12: INSERT INTO orders(id, user_id) VALUES(NULL, 1002); -- ID=1(冲突)

分布式ID生成方案对比

1. 数据库自增ID(改进版)

原理:通过设置不同的自增步长和初始值,使各库生成不重复ID。
配置示例

-- 库1:从1开始,步长2(生成1,3,5...)
SET @@auto_increment_offset = 1; 
SET @@auto_increment_increment = 2;-- 库2:从2开始,步长2(生成2,4,6...)
SET @@auto_increment_offset = 2; 
SET @@auto_increment_increment = 2;

优缺点
✅ 实现简单,依赖数据库原生功能
❌ 扩容困难(新增分片需重新规划步长)
❌ 主从切换可能导致ID重复
❌ 性能瓶颈(单库生成ID)

2. UUID(通用唯一识别码)

原理:基于随机数或时间戳生成全局唯一字符串(如550e8400-e29b-41d4-a716-446655440000)。
Java实现

String uuid = UUID.randomUUID().toString();

优缺点
✅ 无网络开销,性能高
✅ 完全去中心化,生成逻辑简单
❌ 无序字符串,不适合作为索引(影响查询性能)
❌ 存储空间占用大(36字节)
❌ 不具备趋势自增特性(不利于数据库分区分页)

3. Redis发号器

原理:利用Redis的原子操作INCRINCRBY生成唯一ID。
示例代码

// 获取下一个订单ID
Long orderId = redisTemplate.opsForValue().increment("order_id_generator", 1);

优缺点
✅ 高性能(Redis单线程原子操作)
✅ 支持批量生成(减少网络调用)
❌ 依赖外部服务(Redis故障影响ID生成)
❌ 需维护Redis集群,增加系统复杂度

4. Snowflake雪花算法

原理:生成64位长整型ID,结构如下:

1位符号位 | 41位时间戳 | 5位数据中心ID | 5位机器ID | 12位序列号  
  • 时间戳:精确到毫秒级,保证生成的ID按时间趋势递增
  • 机器ID:确保不同服务器生成不同ID
  • 序列号:同一毫秒内生成的不同ID

优缺点
✅ 高性能(本地生成,无网络开销)
✅ 趋势自增(有利于数据库索引优化)
✅ 可自定义位分配(适应不同业务场景)
❌ 依赖系统时钟(时钟回拨可能导致ID重复)
❌ 机器ID需提前规划(分布式环境下需唯一分配)

数据库号段模式(Leaf-Segment)

原理:从数据库批量获取ID号段,本地内存分配,减少数据库访问。
示例

  1. 数据库表存储当前号段的最大值(如max_id=1000
  2. 应用获取号段(如1-1000),本地自增生成ID
  3. 用完后再从数据库获取下一号段(如1001-2000

优缺点
✅ 高性能(本地生成,仅号段用完时访问数据库)
✅ 不依赖时钟
❌ 存在ID空洞(号段未用完时应用重启)
❌ 需数据库表支持

美团Leaf方案

特点:结合Snowflake和号段模式,提供两种ID生成方式:

  1. Leaf-Segment:数据库号段模式,适合对时钟敏感的业务
  2. Leaf-Snowflake:雪花算法,通过ZooKeeper分配机器ID

方案选型建议

方案性能唯一性趋势自增依赖外部服务时钟敏感性适用场景
数据库自增ID✅(数据库)小规模分库(<4个节点)
UUID对ID格式无要求的场景
Redis发号器中高✅(Redis)已有Redis集群的场景
Snowflake高性能、分布式场景
数据库号段模式中高✅(数据库)对时钟回拨敏感的业务

(二)雪花算法(Snowflake)详解

一、雪花算法的本质与起源

定义:由Twitter开源的分布式ID生成算法,通过64位长整型数字(long类型)生成全局唯一、趋势递增的ID。
核心优势

  • 高性能(本地生成,无网络开销)
  • 趋势递增(适合数据库索引优化)
  • 结构可解析(通过ID反推生成时间、机器等信息)
二、64位ID的结构拆解
1位符号位 | 41位时间戳 | 5位数据中心ID | 5位机器ID | 12位序列号  
  • 1位符号位:固定为0(保证生成正数)。
  • 41位时间戳
    • 精确到毫秒,可使用69年((2^41-1)/1000/60/60/24/365 ≈ 69年)。
    • 通常设置为“起始时间戳”(如2015-01-01)与当前时间的差值,避免负数。
  • 5位数据中心ID:最多支持32个数据中心(2^5=32)。
  • 5位机器ID:每个数据中心最多支持32台机器(2^5=32)。
  • 12位序列号:同一毫秒内最多生成4096个ID(2^12=4096)。
三、Java实现示例(含时钟回拨处理)
public class SnowflakeIdGenerator {// 起始时间戳(2021-01-01 00:00:00)private final long startTimestamp = 1609459200000L;// 各部分位数private final long dataCenterIdBits = 5L;  // 数据中心ID位数private final long workerIdBits = 5L;       // 机器ID位数private final long sequenceBits = 12L;     // 序列号位数// 最大取值计算private final long maxWorkerId = -1L ^ (-1L << workerIdBits);  // 31private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);  // 31private final long maxSequence = -1L ^ (-1L << sequenceBits);  // 4095// 位移偏移量private final long workerIdShift = sequenceBits;private final long dataCenterIdShift = sequenceBits + workerIdBits;private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;// 实例变量private long dataCenterId;  // 数据中心IDprivate long workerId;      // 机器IDprivate long sequence = 0L; // 序列号private long lastTimestamp = -1L; // 上次生成ID的时间戳// 构造函数(参数需提前规划分配)public SnowflakeIdGenerator(long dataCenterId, long workerId) {// 参数校验if (dataCenterId > maxDataCenterId || dataCenterId < 0) {throw new IllegalArgumentException(String.format("DataCenter ID can't be greater than %d or less than 0", maxDataCenterId));}if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("Worker ID can't be greater than %d or less than 0", maxWorkerId));}this.dataCenterId = dataCenterId;this.workerId = workerId;}// 同步生成ID(避免并发冲突)public synchronized long nextId() {long timestamp = currentTimeMillis();// 时钟回拨处理(核心坑点)if (timestamp < lastTimestamp) {long offset = lastTimestamp - timestamp;if (offset <= 5) {// 短时间回拨:等待至lastTimestamp后再生成try {wait(offset);timestamp = currentTimeMillis();if (timestamp < lastTimestamp) {throw new RuntimeException("Clock moved backwards. Refusing to generate id.");}} catch (InterruptedException e) {throw new RuntimeException(e);}} else {// 长时间回拨:直接抛异常(需人工处理)throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));}}// 同一毫秒内if (timestamp == lastTimestamp) {// 序列号自增,达到上限则等待下一毫秒sequence = (sequence + 1) & maxSequence;if (sequence == 0) {timestamp = waitNextMillis(lastTimestamp);}} else {// 新毫秒,序列号重置为0sequence = 0L;}lastTimestamp = timestamp;// 组合各部分生成IDreturn ((timestamp - startTimestamp) << timestampShift) |(dataCenterId << dataCenterIdShift) |(workerId << workerIdShift) |sequence;}// 获取当前时间戳private long currentTimeMillis() {return System.currentTimeMillis();}// 等待至下一毫秒private long waitNextMillis(long lastTimestamp) {long timestamp = currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = currentTimeMillis();}return timestamp;}
}
四、雪花算法的核心坑点与解决方案
坑一:分布式环境下workId重复

问题:不同机器分配相同workId,导致生成ID重复。
解决方案

  1. 人工分配:小规模集群手动规划(如数据中心ID=0,机器ID按机房机柜编号分配)。
  2. 自动分配
    • 通过ZooKeeper抢占节点(如在/snowflake/worker_ids下创建临时顺序节点,节点序号作为workId)。
    • 基于数据库表记录已分配的workId,获取时自增+1。
坑二:系统时钟回拨导致ID重复

问题场景

  • 手动修改服务器时间(如NTP时钟同步)。
  • 虚拟机暂停后恢复(CPU时间片调度导致时间回拨)。
    解决方案
  1. 轻度回拨(<5ms)
    • 等待回拨时间后再生成ID(如代码示例中的wait(offset))。
  2. 重度回拨(>5ms)
    • 抛异常阻断业务(适合强一致性场景)。
    • 切换至备用ID生成方案(如UUID),并记录告警。
  3. 预防措施
    • 禁止生产环境手动修改系统时间。
    • 服务器开启NTP自动同步(避免大幅时间偏差)。
五、雪花算法的适用场景

适用场景

  1. 核心业务ID生成:订单号、用户ID、交易ID等(需趋势递增,便于数据库排序)。
  2. 高并发系统:如秒杀、抢购场景(高性能+无网络依赖)。
  3. 分布式微服务:跨节点ID唯一性要求高的场景。

不适用场景

  • 对ID安全性要求高的场景(ID结构可解析,可能泄露业务量等信息)。
  • 对时钟敏感的场景(如金融交易,时钟回拨可能引发严重问题)。
六、与其他ID方案的对比
方案雪花算法UUIDRedis发号器数据库号段
唯一性
趋势递增
性能高(本地计算)高(无网络)中(依赖网络)中(批量获取)
依赖系统时钟Redis集群数据库
时钟敏感✅(回拨需处理)

总结

雪花算法通过“时间戳+机器标识+序列号”的结构,在分布式场景下实现了高性能、唯一且有序的ID生成。其核心挑战在于时钟回拨处理机器ID分配,生产环境中需结合业务特点制定针对性方案。对于追求高性能和ID有序性的场景,雪花算法是首选;若对时钟敏感或ID安全性要求高,则需考虑其他方案(如数据库号段或UUID)。

http://www.xdnf.cn/news/1064647.html

相关文章:

  • JVM(10)——详解Parallel垃圾回收器
  • python高校教务管理系统
  • 超详细YOLOv8/11图像菜品分类全程概述:环境、数据准备、训练、验证/预测、onnx部署(c++/python)详解
  • TypeScript类型定义:Interface与Type的全面对比与使用场景
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(三十六) -> 配置构建(三)
  • 算法导论第二十五章 深度学习的伦理与社会影响
  • C4.5算法深度解析:决策树进化的里程碑
  • 怎么让二级域名绑定到wordpesss指定的页面
  • 0-机器学习简介
  • winform mvvm
  • opencv 之双目立体标定算法核心实现
  • STM32F103C8T6,窗口看门狗(WWDG)与独立看门狗(IWDG)详解
  • all()函数和any()函数
  • 【机器学习四大核心任务类型详解】分类、回归、聚类、降维智能决策指南
  • 【投稿与写作】overleaf 文章转投arxiv流程经验分享
  • 开发语言本身只是提供了一种解决问题的工具
  • Windows 后渗透中可能会遇到的加密字符串分析
  • C++结构体初始化与成员函数实现语法详解
  • webpack+vite前端构建工具 -6从loader本质看各种语言处理 7webpack处理html
  • c#websocket心跳包自定义实现,支持异步操作的取消
  • RN(React Native)技术应用中常出现的错误及解决办法
  • 可理解性输入:洗澡习惯
  • 【设计模式】策略模式 在java中的应用
  • 《Redis》事务
  • idea2023+zulu-jdk+maven3.9.10
  • 【后端】负载均衡
  • 解决OSS存储桶未创建导致的XML错误
  • LLMs之MCP:excel-mcp-server的简介、安装和使用方法、案例应用之详细攻略
  • 5.3 VSCode使用FFmpeg库
  • 一,python语法教程.内置API