本文目录导读:

这是一个非常经典的分布式系统设计问题,首先需要明确:“全局唯一”和“全局单调递增”是两个不同的目标,且在分布式系统中往往需要权衡。
根据你的需求,我将其分为三个等级,并给出对应的生成策略。
全局唯一,且趋势递增
这是最常见的需求(如数据库主键、订单号等),不需要严格递增(即下一秒的ID必须大于上一秒),只要整体呈上升趋势,便于数据库B+树索引维护即可。
雪花算法及其变体
- 原理:64位Long型整数。
1位符号位 + 41位时间戳 + 10位工作机器ID + 12位序列号。
- 特点:
- 唯一:通过数据中心ID + 机器ID + 毫秒内自增序列保证。
- 趋势递增:时间戳在高位,所以整体趋势递增。
- 无状态:服务本地生成,无网络IO。
- 缺点:依赖机器时钟,时钟回拨会导致ID重复或乱序。
- 变体:
- 百度的uid-generator:将机器ID替换为自增ID,减弱时钟依赖。
- 美团的Leaf:优化了时钟回拨问题,提供snowflake和segment两种模式。
数据库号段模式(Segment)
- 原理:集中式数据库分发批次。
- 在数据库表中维护一个
max_id字段。 - 应用每次从数据库申请一段ID(如
[1000, 2000))。 - 应用本地内存中生成该段内的ID。
- 在数据库表中维护一个
- 特点:
- 有序:按申请顺序严格递增。
- 高可用:本地生成,减少数据库压力。
- 易于回滚:即使应用崩溃,已分配的ID段不会浪费太多。
- 缺点:依赖中央数据库(如MySQL)的可用性。
全局唯一,且严格单调递增
注意: 在分布式场景下,严格单调递增(即每个ID比前一个ID大1)几乎无法在无锁、高性能的情况下实现,它强制所有生成节点必须串行化。
- 核心方案:单点串行化。
- 使用一个Redis
INCR命令、或一个数据库的自增ID。 - 代价:完全失去并发性能,成为全局瓶颈。
- 使用一个Redis
除非你的并发量极低(< 100 QPS),否则不要追求严格单调递增。
强要求有序(如排序、流水号)
如果需要生成的ID具备某种业务语义(如日期 + 序号),可以用以下方式组合:
-
日期前缀 + 自增后缀
- 格式:
YYYYMMDD + 固定位数序列号 - 实现:Redis每日一个Key(
INCR order:20231027),或数据库每日一个计数器。 - 缺点:高并发下需注意Redis的单点瓶颈。
- 格式:
-
Lua脚本保证原子性
在Redis中执行Lua脚本,同时生成时间和序列,避免并发冲突。
总结与推荐
| 需求等级 | 推荐方案 | 优劣势 |
|---|---|---|
| 唯一 + 趋势递增 | 雪花算法(Snowflake) | 高效,无网络依赖,适合常规分布式场景。 |
| 唯一 + 趋势递增 + 强可用 | 美团Leaf / 百度uid-generator | 解决了雪花算法的时钟回拨问题。 |
| 唯一 + 严格递增(低并发) | 数据库自增ID / Redis INCR | 简单,但性能受限。 |
| 高并发 + 业务有序 | 日期前缀 + Redis自增 | 适合有业务含义的流水号。 |
一句话建议:
- 99%的场景用雪花算法(或它的成熟变体)。
- 如果无法容忍ID位数过长,改用数据库号段模式。
- 只有当你确定需要“下一个ID比上一个正好大1”且并发小于几千时,才考虑单点串行化方案。