分库分表后ID冲突的解决方案
分库分表后,传统的自增ID会导致ID冲突,需要采用分布式ID生成方案来保证全局唯一性。以下是几种常见的解决方案:
UUID方案
UUID(Universally Unique Identifier)是一个128位的数字,通常以36字符的字符串表示。UUID的生成不依赖中心节点,本地即可生成,具有极高的唯一性。
优点:
- 实现简单,无需中心化协调
- 扩展性好,适合分布式环境
缺点:
- UUID过长,占用存储空间大
- 无序性导致索引效率低
- 无法保证趋势递增
数据库自增ID方案
利用独立的数据库实例生成自增ID,各业务系统通过访问该数据库获取ID。
实现方式:
- 创建专门用于生成ID的数据库
- 建立表结构并设置自增主键
- 各业务系统通过接口获取ID
优点:
- 实现简单,ID有序递增
- 可保证全局唯一性
缺点:
- 单点故障风险
- 性能瓶颈,高并发场景下可能成为系统瓶颈
- 需要维护额外的数据库实例
Redis生成方案
利用Redis的INCR命令实现ID的原子性递增。
实现方式:
- 使用Redis的INCR命令生成ID
- 可设置不同key区分业务类型
- 可结合时间戳实现更复杂的ID结构
优点:
- 性能高,Redis单机可支持10万+/秒的吞吐量
- 实现相对简单
缺点:
- 需要引入Redis依赖
- 存在单点故障风险(可通过集群缓解)
雪花算法(Snowflake)
Twitter开源的分布式ID生成算法,生成64位的Long型ID。
ID结构:
- 1位符号位(始终为0)
- 41位时间戳(毫秒级)
- 10位机器ID(5位数据中心ID + 5位机器ID)
- 12位序列号(毫秒内的计数)
实现示例(Java):
public class SnowflakeIdWorker {
private long workerId;
private long datacenterId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
}
优点:
- ID趋势递增,适合作为索引
- 不依赖第三方组件
- 性能高,单机每秒可生成26万ID
缺点:
- 依赖机器时钟,时钟回拨会导致ID重复
- 需要配置机器ID和数据中心ID
美团Leaf方案
美团开源的分布式ID生成系统,提供两种模式:
- Leaf-segment:基于数据库号段模式
- Leaf-snowflake:基于雪花算法优化
优点:
- 解决了雪花算法的时钟回拨问题
- 号段模式减少数据库访问
- 提供高可用保障
缺点:
- 系统复杂度较高
- 需要部署和维护Leaf服务
滴滴TinyID方案
滴滴基于数据库号段模式实现的分布式ID系统,特点:
- 支持HTTP和RPC两种访问方式
- 支持多DB配置,提高可用性
- 提供ID趋势递增
优点:
- 使用简单,接入成本低
- 性能较好,减少数据库访问
- 支持高可用部署
缺点:
- 依赖数据库
- 系统扩展需要考虑数据库性能
最佳实践建议
- 中小型系统可优先考虑雪花算法,实现简单且性能好
- 大型分布式系统可考虑Leaf或TinyID等成熟方案
- 对顺序要求不高的场景可使用UUID
- 需要严格递增且可读性强的场景可考虑数据库自增方案
实施时需考虑:
- 系统规模和增长预期
- 性能要求
- 运维复杂度
- 可用性要求
以上方案各有优劣,应根据具体业务场景和技术栈选择最适合的方案。
144

被折叠的 条评论
为什么被折叠?



