分库分表ID冲突解决方案

前端异常监控体系设计与实践

核心问题

在单库单表时,我们通常使用数据库的自增主键(AUTO_INCREMENT),这可以保证ID的唯一性和递增性。但在分库分表后,如果每个分片还使用自己的自增ID,就会出现下图所示的情况:

text

数据库1: Table_1 -> ID: 1, 2, 3, 5, 7...
数据库2: Table_2 -> ID: 1, 2, 4, 6, 8...

从全局来看,ID不再是唯一的,也无法保证全局递增。


解决方案

主要有以下几类方案,从应用层到数据库层,各有优劣。

1. UUID

使用UUID(Universally Unique Identifier)作为主键。

  • 实现:在应用层生成一个36位的字符串(如 550e8400-e29b-41d4-a716-446655440000)。

  • 优点

    • 生成简单:本地生成,无需中心化节点或网络调用,性能极高。

    • 全局唯一:理论上重复的概率极低。

  • 缺点

    • 存储空间大:通常为36位字符串,占用存储空间大。

    • 无序性:这是最严重的缺点。作为主键插入时,会导致频繁的页分裂,严重影响写入性能。

    • 不直观:不具备可读性,不利于调试。

结论:不推荐作为分布式主键的首选,除非对性能和存储要求不高的场景。

2. 数据库序列(如TDDL Sequence)

依赖一个独立的数据库来生成全局唯一的序列号。

  • 实现:单独搭建一个数据库实例,创建一个sequence表,通过replace into或事务来获取一批ID,然后加载到应用内存中。

  • 优点

    • ID是数字,递增,可读性好。

  • 缺点

    • 存在单点故障和性能瓶颈:所有ID生成都依赖这个中心数据库。

    • 架构复杂:需要维护一个高可用的序列数据库。

结论:是一种经典的方案,但现代互联网架构中已较少使用,因为存在单点风险。

3. Snowflake(雪花算法)及其变种 - 推荐

Twitter开源的分布式ID生成算法,是目前最流行的方案。

  • 原理:生成一个64位的Long型数字,其结构如下:

    text

    0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
    • 1位符号位:恒为0。

    • 41位时间戳(毫秒):可以使用约69年。

    • 10位工作机器ID(Data Center ID + Worker ID):最多支持1024个节点。

    • 12位序列号:每毫秒每个节点可以生成4096个ID。

  • 实现

    • 可以在应用服务中直接集成算法,为每个服务实例配置唯一的WorkerID

    • 也可以部署为独立的ID生成服务(如UidGenerator、Leaf-snowflake),通过RPC调用获取ID。

  • 优点

    • 高性能:本地生成,无网络消耗。

    • 趋势递增:ID是时间上有序的,对数据库的索引友好。

    • 容量大:每秒可生成数百万个ID。

  • 缺点

    • 时钟回拨问题:如果服务器时钟发生回拨,可能导致生成重复ID。需要在代码中处理。

    • Worker ID分配:需要保证每个节点的Worker ID是全局唯一的。

结论这是目前最主流、最推荐的方案。很多大厂都基于Snowflake做了自己的变种。

4. Leaf - 美团开源

Leaf是美团开源的分布式ID生成系统,它融合了两种模式,是对Snowflake方案的增强。

  • Leaf-snowflake模式

    • 通过使用ZooKeeper来管理Worker ID,解决了手动配置的麻烦。

    • 针对时钟回拨做了优化处理:在启动时和运行时都会检查时钟,如果回拨时间较短,会等待时钟追平;如果回拨时间过长,则直接拒绝服务,等待人工介入。

  • Leaf-segment模式

    • 可以看作是“数据库序列”方案的优化版。它不再每次获取一个ID,而是利用数据库批量获取一个号段(如1~1000)。

    • 服务将整个号段加载到内存中,然后逐个分配。分配完后再去数据库获取下一个号段。

    • 优点:大大降低了数据库的读写压力(获取1000个ID才访问一次DB),性能极高,且保证了ID的绝对递增。

    • 缺点:ID不是连续的,而是以一个号段(如1000)为单位递增。在服务重启时,会丢弃当前号段中未使用的ID,造成ID空洞。

结论:Leaf是一个生产级的、非常成熟的解决方案,集成了两种模式的优点,是Snowflake方案的优秀实现。

5. 号段模式(Segment)

正如在Leaf-segment中提到的,这是一种非常高效且简单的方案。

  • 实现:在数据库中维护一张表,记录biz_tag(业务类型)和对应的最大ID(max_id)。每次更新max_id = max_id + step(步长),并返回新的号段范围。

  • 优点

    • 高性能:数据库压力小。

    • 可以生成全局递增的ID

    • 实现简单,非常灵活。

  • 缺点

    • ID不是连续递增的,而是以号段为单位。

    • 服务重启可能导致ID浪费。

结论:非常适合对ID递增性要求高,且能容忍少量ID浪费的场景。


方案对比总结

方案优点缺点适用场景
UUID实现简单,本地生成,性能高存储大,无序,影响插入性能对性能要求不高的简单应用,非核心业务
数据库序列ID递增,数字类型有单点故障和性能瓶颈传统企业应用,数据量不大
Snowflake高性能,趋势递增,容量大有时钟回拨问题绝大多数互联网分布式场景
Leaf/号段高性能,高可用,可全局递增实现稍复杂,ID非绝对连续对ID递增性有严格要求的高并发场景

如何选择?

  1. 如果你的系统是典型的互联网分布式架构,并发量高

    • 首选 Snowflake 或其变种(如Leaf-snowflake)。它几乎是为这种场景量身定做的。

  2. 如果你对ID的全局绝对递增有强需求(如金融交易订单)

    • 可以考虑 Leaf-segment(号段模式) 或 基于数据库的号段模式

  3. 如果你的系统规模不大,想快速上手

    • 可以使用 Redis的INCR命令(原理类似数据库序列,但性能更好)或者简单的号段模式

  4. 不推荐使用纯UUID和原始的数据库序列作为分库分表下的主键方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值