什么是双ID链法
双ID链法通过 组合两种不同特性的ID生成器(通常选择互补的方案),生成一个复合ID,利用两者的优势,规避各自的缺陷
最常见的组合是:
- 主ID:全局唯一但可能不连续(如雪花算法/Snowflake)
- 副ID:局部有序且绝对连续(如数据库自增序列、Redis INCR)
ID结构示例:
[主ID(雪花算法)]_[副ID(自增序列)]
↓ ↓
保证全局唯一性 保证局部连续性
例如:1256892300152455168_42
(前半部分是雪花ID,后半部分是Redis自增序列)
双ID是放到消息题里面作为标识的
为什么需要双ID链法
单一ID生成器的缺陷:
ID生成方案 | 优点 | 缺点 |
雪花算法 | 高性能、分布式唯一 | 时钟回拨可能导致重复 |
数据库自增序列 | 绝对有序 | 性能低、单点故障风险 |
Redis INCR | 高并发 | 主从切换可能重复 |
双ID链的解决思路:
- 主ID(如雪花算法):解决分布式唯一性问题
- 副ID(如Redis的Incr):解决连续性、防时钟回拨问题
两者结合后:
- 即使主ID因时钟回拨重复,副ID也能保证整体唯一性
- 即使副ID因主从切换不连续,主ID仍能保证全局唯一
双ID链法解决的核心问题
1. 主从切换后的ID重复
问题:Redis主节点宕机后,从节点可能重复分配已丢失的ID
解决:即使副ID重复,主ID(雪花算法)的机器ID和时间戳仍不同
2. 时钟回拨问题
问题:雪花算法依赖系统时钟,时钟回退可能导致ID重复
解决:副ID的自增序列不受时钟影响,可作为回拨时的补偿
3. 消息重复消费
问题:消费者可能重复处理同一条消息
解决:用双ID作为消息唯一键,消费端可同时校验主ID和副ID
用雪花算法解决分布式唯一性问题
用自增序列解决局部有序性问题
两者组合后,即使一个组件失效,另一个仍能兜底
与MQ幂等性的协作
即使使用双ID作为Key,仍需配合MQ的幂等机制:
防护层级 | 作用 | 实现示例 |
双ID | 生产端避免生成重复ID | 雪花ID+自增ID |
生产者幂等 | Broker端去重 | Kafka设置 |
消费者幂等 | 业务层最终防重 | 数据库唯一约束或Redis判重 |
Kafka设置 enable.idempotence=true
如何实现生产者幂等
当启用 enable.idempotence=true
时,Kafka 会为每个生产者分配一个唯一标识,并跟踪每条消息的状态:
要素 | 作用 | 类比现实场景 |
PID (Producer ID) | 每个生产者实例的唯一ID,由Broker分配 | 就像快递员的工号,唯一标识发货人 |
Sequence Number | 每条消息的递增序列号(从0开始),按 维度维护 | 快递员每次发货的包裹编号 |
Epoch | 生产者实例的版本号(防止僵尸生产者) | 工号的有效期(防冒用) |
这可以实现在Broker端,也就是MQ端本身进行去重复
新消息:必须满足 当前Seq == Broker存储的LastSeq + 1
(如Broker记录的最后Seq=5,则下一条必须为6)
重复消息:收到 Seq ≤ LastSeq
的消息直接丢弃
(如重复发送Seq=5,而Broker已处理到Seq=7)
Redis生成副ID-避免Redis单点故障主从切换时的数字漏洞
问题:
只依赖Redis的incr生成ID的话,在Redis 主节点故障切换后,incr 生成的 ID 可能重复(主节点未同步的命令丢失),导致 “订单 ID 冲突”
方案 1:单纯 Redis Key + 过期时间
完全依赖 Redis 生成 ID,主从切换时必然存在 ID 重复风险(除非关闭主从异步复制,牺牲性能)
方案 2:双 ID 链(主 ID 用雪花算法)
主 ID 由雪花算法生成(基于时间戳 + 机器码),不依赖 Redis,天然避免主从切换导致的 ID 重复。辅助 ID 用于 Redis 分片,即使 Redis 主从切换,辅助 ID 短暂重复也不影响主 ID 的唯一性(业务逻辑以主 ID 为准)
双ID链法优化大Key问题
用副id将双ID进行分片存储,控制不同id存储的位置
例如下面就是一个双ID:
1256892300152455168_42
例如我们以1000数量为一组
42/1000=0
1001/1000=1
2001/1000=2
那么就是副id为0-999的双id存在 xxxKey1
那么就是副id为1000-1999的双id存在 xxxKey2
那么就是副id为2000-2999的双id存在 xxxKey3
这样子就优化了大Key问题