Skip to content

分布式 ID

分布式 ID 是在分布式环境中生成全局唯一标识符的方案。分布式 ID 需要保证唯一性、有序性、性能,广泛应用于订单 ID、用户 ID、消息 ID。

为什么需要分布式 ID

单机数据库的自增 ID 在分布式环境下无法保证唯一性,多个数据库的自增 ID 可能冲突。分布式 ID 需要保证全局唯一。

分布式 ID 的应用场景:订单 ID、用户 ID、消息 ID、流水号、主键 ID。

分布式 ID 的设计要求

全局唯一:ID 在全局范围内唯一,不会冲突。

有序性:ID 趋势递增,有利于数据库索引(B+ 树的插入性能)。

高性能:生成 ID 的性能要高,不能成为瓶颈。

高可用:ID 生成服务故障不影响业务。

信息量:ID 可以包含时间、机器 ID 等信息,便于排查问题。

UUID

UUID 的原理

UUID(Universally Unique Identifier)是 128 位的唯一标识符,通常由 36 个字符表示(包含 4 个连字符)。UUID 的版本:UUID v1(基于时间和 MAC 地址)、UUID v4(基于随机数)。

UUID v1 的组成:时间戳(60 位)、时钟序列(14 位)、节点 ID(48 位,MAC 地址)。UUID v4 的组成:随机数(122 位)、版本号(4 位)、变体号(2 位)。

UUID 的问题

无序性:UUID v1 虽然基于时间,但不同机器的时钟不同,无法保证全局有序。UUID v4 完全随机,无序。

长度过长:UUID 是 36 个字符(包含连字符),存储和传输开销大。

性能差:UUID 是字符串,无法作为数据库主键的聚簇索引(InnoDB 的主键是聚簇索引,无序主键会导致页分裂)。

信息泄露:UUID v1 包含 MAC 地址,可能泄露机器信息。

数据库自增 ID

单机自增 ID

单机数据库的自增 ID 可以保证唯一和有序,但只能单机使用,无法横向扩展。

分布式自增 ID

步长模式

步长模式是设置不同数据库的自增步长和起始值。例如 3 个数据库,步长为 3,起始值分别为 1、2、3,生成的 ID 为 1、4、7,2、5、8,3、6、9。

步长模式的问题:扩展困难,新增数据库需要重新计算步长。ID 不连续,浪费 ID。

集群 ID

集群 ID 是使用数据库集群的自增 ID,如 MySQL 的 auto_increment_increment 和 auto_increment_offset。不同实例设置不同的 offset,保证 ID 不冲突。

数据库自增 ID 的问题

性能瓶颈:数据库自增 ID 需要访问数据库,性能受限于数据库的并发能力。

单点故障:数据库故障会导致 ID 生成服务不可用。

Redis 生成 ID

Redis INCR

Redis 的 INCR 命令可以原子递增,保证 ID 唯一和有序。例如 INCR id:generator,返回值就是新 ID。

Redis 的问题

性能瓶颈:Redis 是单线程的,虽然性能很高,但仍然是瓶颈。

持久化问题:Redis 的 AOF 持久化可能丢失数据,导致 ID 重复。需要等待命令同步到磁盘后再返回,但性能会下降。

单点故障:Redis 故障会导致 ID 生成服务不可用。需要 Redis 集群,但集群的 INCR 需要槽迁移,复杂度高。

雪花算法(Snowflake)

雪花算法的原理

雪花算法是 Twitter 开源的分布式 ID 算法,生成 64 位的 Long 型 ID。雪花算法的组成:符号位(1 位,0)、时间戳(41 位,毫秒级)、机器 ID(10 位,数据中心 ID 5 位 + 工作节点 ID 5 位)、序列号(12 位,毫秒内的计数器)。

时间戳(41 位):可以使用 69 年(2^41 毫秒约 69 年)。时间戳的 epoch(起始时间)可以自定义,如 2020-01-01 00:00:00。

机器 ID(10 位):可以支持 1024 个节点(32 个数据中心 × 32 个工作节点)。机器 ID 需要手动配置或通过 ZooKeeper 分配。

序列号(12 位):每毫秒可以生成 4096 个 ID(2^12)。

雪花算法的优势

高性能:本地生成 ID,无需访问网络,性能很高(每秒百万级)。

有序性:ID 趋势递增,有利于数据库索引。

信息量:ID 包含时间、机器 ID 等信息,便于排查问题。

雪花算法的问题

时钟回拨:如果机器时钟回拨,可能导致 ID 重复。解决方案:等待时钟追上、使用备用机器 ID、记录上次分配时间戳。

机器 ID 分配:需要手动配置机器 ID,容易冲突。解决方案:使用 ZooKeeper 分配机器 ID。

时间戳上限:41 位时间戳只能使用 69 年,超过后需要重置 epoch。

雪花算法的实现

Snowflake(Twitter):Java 实现的雪花算法,但已不再维护。

UidGenerator(百度):基于雪花算法,解决了时钟回拨问题,使用 WorkerID 分配。

Leaf(美团):基于雪花算法,使用 ZooKeeper 分配 WorkerID,支持号段模式。

TinyID(滴滴):基于数据库预分配号段,高性能。

号段模式

号段模式的原理

号段模式是预分配一段 ID,缓存到内存,使用完后再分配新的号段。例如数据库存储当前最大 ID 和步长,应用启动时获取一段 ID(如 1000-2000),缓存在内存,使用完后再获取下一段 ID。

号段模式的优势:性能高,无需每次访问数据库。可用性高,数据库故障时仍可以使用缓存的号段。

号段模式的问题:ID 不连续,回滚的 ID 无法使用。需要双 buffer 优化,避免获取新号段时的性能抖动。

Leaf Segment

Leaf Segment 是美团开源的分布式 ID 方案,使用号段模式。Leaf Segment 双 buffer 优化:一个 buffer 使用时,另一个 buffer 异步加载新号段,避免获取新号段时的性能抖动。

Leaf Segment 的问题:如果服务重启,未使用的号段会浪费。

分布式 ID 的对比

方案唯一性有序性性能复杂度
UUID无序
数据库自增有序
Redis INCR有序
雪花算法有序
号段模式无序

分布式 ID 的最佳实践

雪花算法是首选:雪花算法性能高、有序性好、信息量大,适合大多数场景。

号段模式适合高并发:号段模式可以预分配,避免数据库压力,适合高并发场景。

UUID 可以用但需谨慎:UUID 无序、长度长,不适合作为数据库主键,但适合作为业务 ID(如订单号)。

时钟同步很重要:雪花算法依赖时钟,需要使用 NTP 同步时钟,并处理时钟回拨。

机器 ID 管理:雪花算法的机器 ID 需要管理,可以使用配置文件、ZooKeeper、数据库分配。

分布式 ID 是分布式系统的基础组件,理解分布式 ID的原理和权衡,有助于设计合适的 ID 生成方案。