核心问题
在单库单表时,我们通常使用数据库的自增主键(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递增性有严格要求的高并发场景 |
如何选择?
-
如果你的系统是典型的互联网分布式架构,并发量高:
-
首选 Snowflake 或其变种(如Leaf-snowflake)。它几乎是为这种场景量身定做的。
-
-
如果你对ID的全局绝对递增有强需求(如金融交易订单):
-
可以考虑 Leaf-segment(号段模式) 或 基于数据库的号段模式。
-
-
如果你的系统规模不大,想快速上手:
-
可以使用 Redis的INCR命令(原理类似数据库序列,但性能更好)或者简单的号段模式。
-
-
不推荐使用纯UUID和原始的数据库序列作为分库分表下的主键方案。
前端异常监控体系设计与实践
128

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



