分布式数据库全局唯一ID生成,核心要解决的问题就一个:多个节点、多个服务同时写数据时,怎么保证ID不重复、有序、高性能。目前主流方案无非这几种——UUID、数据库自增序列、Redis/Zookeeper号段模式、雪花算法(Snowflake)及其变体、号段模式(Leaf/UidGenerator)。没有万能方案,只有适合你业务场景的取舍。下面我把每种方案的原理、优缺点、适用场景全部拆开讲清楚,你直接对照自己的系统选。

一、UUID:最简单但最不推荐的方案

UUID(Universally Unique Identifier)是128位的标识符,通常以36字符的字符串形式呈现,比如"550e8400-e29b-41d4-a716-446655440000"。它的生成几乎不需要任何协调,本地就能算出来,天然全局唯一。

但问题非常明显。第一,它是无序的,插入B+树索引时会导致大量页分裂,写入性能急剧下降。第二,占16字节,比8字节的BIGINT大一倍,索引膨胀严重。第三,不可读,没法从ID里提取任何业务信息。如果你只是做临时标识、非主键场景,UUID可以用;但如果做主键,基本上是给自己挖坑。

二、数据库自增ID:单库能用,分布式直接废掉

最传统的方案,MySQL的AUTO_INCREMENT,简单可靠。但在分布式环境下,多个数据库实例各自自增,ID一定会冲突。有人说"设置不同起始值和步长",比如节点A从1开始步长2,节点B从2开始步长2。这确实能避免冲突,但扩展性极差——加一个节点就要重新规划所有节点的步长,运维成本高,而且一旦某个节点宕机,它负责的那部分ID就浪费了。

所以数据库自增只适合单库单表、没有分库分表需求的小型系统。一旦上了分布式,直接放弃。

三、Redis/Zookeeper集中式发号:强一致但有单点瓶颈

思路很直接:用一个中心化的服务(Redis的INCR命令,或者Zookeeper的顺序节点)来统一分配ID。每次请求来了,中心服务加1返回。实现简单,ID严格递增。

但瓶颈也很明显。所有ID生成请求都打到一个点上,高并发下Redis或ZK本身会成为瓶颈。虽然Redis单机每秒能处理几万次INCR,但如果你的业务QPS到了十万、百万级别,单点就扛不住了。而且中心服务一旦挂了,整个系统的ID生成就停了,可用性是个大问题。通常需要做主从切换、哨兵集群来兜底,但复杂度也上去了。

// Redis INCR 方式示例
long id = jedis.incr("order_id");

这种方案适合中等并发、对ID连续性有要求、且能接受一定运维复杂度的场景。

四、号段模式(Leaf/UidGenerator):工程化最成熟的方案

号段模式的核心思想是"批量预取"。应用服务不是每次都去中心服务拿一个ID,而是一次拿一段(比如1000个),缓存在本地内存里慢慢用。用完了再去取下一段。这样大幅降低了对中心服务的请求频率。

美团的Leaf框架就是这个思路,支持号段模式和Snowflake模式两种。号段模式下,DB或Redis里存一个max_id,应用每次取一段:

// 号段模式伪代码
current_max_id = SELECT max_id FROM id_generator;
next_max_id = current_max_id + step;  // step=1000
UPDATE id_generator SET max_id = next_max_id WHERE max_id = current_max_id;
// 本地缓存 [current_max_id+1, next_max_id] 逐步分配

优点是性能高、ID有序、实现相对简单。缺点是多个应用实例各自缓存号段,如果某个实例宕机,它缓存里还没用完的ID就浪费了(可以通过设置号段长度和双buffer优化来缓解)。另外,如果中心DB挂了,所有服务都拿不到新号段,需要做高可用。

百度的UidGenerator也是类似思路,但做了RingBuffer预加载,进一步优化了性能和可用性。适合中大规模分布式系统,是目前互联网公司用得最多的方案之一。

五、雪花算法(Snowflake):高性能但有时钟回拨风险

雪花算法是Twitter开源的,核心是把64位ID分成几段:1位符号位、41位时间戳、10位机器ID、12位序列号。结构如下:

| 1bit | 41bit timestamp | 10bit machineId | 12bit sequence |

41位时间戳可以用69年,10位机器ID支持1024个节点,12位序列号每毫秒支持4096个ID。本地生成、无需网络请求、高性能、ID大致有序(按时间递增)。

但雪花算法有个致命问题——时钟回拨。如果服务器时间往回跳了(NTP同步、手动改时间都可能),就可能生成重复ID。解决办法有几种:一是等待时钟追上来再生成;二是用逻辑时钟替代物理时钟;三是在机器ID里加入时间戳版本号,回拨时切换。但这些都增加了复杂度。

另外,标准雪花算法的10位机器ID在大规模集群(超过1024节点)时不够用,需要扩展。国内很多公司做了变体,比如把机器ID位数加大、或者用IP段+进程ID组合来编码。

六、各方案核心维度对比

我把几种方案从关键维度做个横向对比,方便你做决策:

唯一性保障:UUID和雪花算法是算法层面保证,号段模式和Redis是架构层面保证,数据库自增是单点保证。分布式场景下,前三者都能做到,数据库自增不行。

性能表现:雪花算法 > 号段模式 > Redis集中式 > 数据库自增 > UUID(作为主键时)。雪花算法纯本地计算,性能天花板最高;号段模式有本地缓存,性能也很好;Redis集中式受网络和单点限制。

有序性:数据库自增和Redis严格有序,号段模式基本有序,雪花算法按时间大致有序(同一毫秒内无序),UUID完全无序。

可用性:雪花算法和UUID不依赖外部服务,可用性最高;号段模式依赖中心DB但有缓存兜底;Redis集中式依赖中心服务;数据库自增依赖单库。

扩展性:雪花算法和UUID天然支持水平扩展;号段模式需要提前规划机器ID;Redis和数据库自增扩展成本高。

七、实际选型建议:按场景对号入座

小项目、快速开发、不在乎性能:UUID凑合能用,但别当主键。

中等规模、需要有序ID、能接受一定运维:号段模式(Leaf或UidGenerator)是首选,工程成熟、社区活跃、坑都被踩过了。

高并发、对延迟极度敏感、不需要严格连续:雪花算法或其变体最合适,本地生成零网络开销。但一定要处理好时钟回拨问题。

对ID连续性有强要求、并发不高:Redis INCR或数据库自增+步长方案,简单直接。

还有一种混合策略:用雪花算法生成ID,但把时间戳部分换成自定义的业务编码(比如分库分表的sharding key),既保证高性能又兼顾业务路由需求。这在电商、金融场景很常见。

八、容易被忽略的细节

第一,ID长度。如果你用BIGINT(8字节),雪花算法天然适配;如果用VARCHAR存UUID,索引效率差很多。分布式场景下尽量用数值型ID。

第二,ID泄露问题。连续递增的ID容易被爬虫遍历,如果是订单号、用户ID这种对外暴露的字段,建议在ID外面再套一层混淆(比如用Feistel加密或者加随机偏移),或者直接用雪花算法那种非严格连续的ID。

第三,多机房部署。如果你有多个数据中心,机器ID的分配要全局规划,避免不同机房的机器ID冲突。Leaf和UidGenerator都支持通过Zookeeper或数据库来统一分配机器ID。

第四,监控和告警。号段模式下要监控号段消耗速度,如果消耗异常快说明有bug或者流量突增;雪花算法要监控时钟偏移。这些运维细节不做好,线上出问题很难排查。

总结一句话:没有最好的ID生成方案,只有最适合你当前架构和业务阶段的方案。初创期别过度设计,先用号段模式或雪花算法跑起来;规模大了再根据实际瓶颈做针对性优化。分布式ID这件事,选型只是第一步,真正的功夫在后续的监控、容灾和持续调优上。