分布式 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 生成方案。