type
status
date
slug
summary
tags
category
icon
password
URL
文章来源说明
分布式唯一ID实战
背景
在日常开发中,我们经常需要为系统中的各种数据生成唯一的ID,比如用户ID、商品ID、订单ID等。这些ID不仅需要在全局范围内唯一,而且在一些情况下还需要具备递增性和信息安全性。
另外在唯一id要求下一般都会要求自增,因为数据库默认索引是b+树节点 是有顺序,如果id没有顺序要维护这棵树需要不停的合并和分裂
传统上,数据库自增主键常被用作唯一ID,但在分布式系统中,尤其是分库分表的场景下,数据库自增ID已经无法满足需求。因此,开发一个能够生成全局唯一ID的系统显得尤为重要。
常见的分布式唯一ID生成方案
1. UUID
UUID (Universally Unique Identifier) 是一种常见的唯一ID生成方式。UUID由32个十六进制数字组成,通常以连字号分为五段,形式为
8-4-4-4-12的36个字符。优点:
- 高性能:UUID在本地生成,不依赖网络,因此性能非常高。
缺点:
- 存储效率低:UUID太长,占用16字节128位,通常以36字符的字符串表示,存储和索引效率较低。
- 信息不安全:部分UUID生成算法基于MAC地址,可能会泄露设备信息。
2. 雪花算法 (Snowflake)
雪花算法 是Twitter开源的一种分布式ID生成算法,将64位的二进制数划分为多个部分,用于表示时间戳、机器ID和自增序列等信息。
Snowflake 把 64-bit 分别划分成多段,分开来标示机器、时间等,比如
在 snowflake 中的 64-bit 分别表示如下图所示:

第 0 位: 符号位(标识正负),始终为 0,没有用,不用管。
第 1~41 位 :一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41
毫秒(约 69 年)
第 42~52 位 :一共 10 位,一般来说,前 5 位表示机房 ID,后 5 位表
示机器 ID(实际项目中可以根据实际情况调整),这样就可以区分不同集群/机
房的节点,这样就可以表示 32 个 IDC,每个 IDC 下可以有 32 台机器。 第 53~64 位 :一共 12 位,用来表示序列号。 序列号为自增值,代表单
台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最
多可以生成 4096 个 唯一 ID。
理论上 snowflake 方案的 QPS 约为 409.6w/s,这种分配方式可以保证在任何
一个 IDC 的任何一台机器在任意毫秒内生成的 ID 都是不同的。
有很多基于 Snowflake 算法的开源实现比如美团的 Leaf、百度的
UidGenerator(自 18 年后,UidGenerator 就基本没有再维护了,
https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md),并且这
些开源实现对原有的 Snowflake 算法进行了优化。在实际项目中,我们一般也
会对 Snowflake 算法进行改造,最常见的就是在算法生成的 ID 中加入业务类型
信息。
优点:
- 趋势递增:ID按时间递增,适合排序和索引。
- 高性能:本地生成ID,不依赖外部系统。
缺点:
- 依赖机器时钟:如果服务器时间发生回拨,可能会生成重复的ID。
3. 数据库生成
使用数据库(如MySQL)的自增主键来生成唯一ID。通过设计步长和多台服务器协同工作,可以生成分布式唯一ID。
优点:
- 简单易实现:利用现有的数据库系统即可实现。
缺点:
- 并发性能差:需要频繁访问数据库,且存在单点故障风险。
4. Redis生成
通过Redis的
incr命令可以实现顺序递增的ID生成。Redis Cluster可以提升并发性能,并保证高可用性。优点:
- 高性能:基于内存操作,生成速度快。
缺点:
- 数据丢失风险:即使开启持久化,仍存在数据丢失的可能,可能会导致ID重复。
分布式ID微服务:美团Leaf的实现
美团开发了Leaf分布式ID生成系统,主要包含Leaf-segment(基于数据库的方案)和Leaf-snowflake(基于雪花算法的方案)两种实现。
1. Leaf-segment方案
Leaf-segment方案对传统的数据库自增主键进行了优化,通过批量获取ID段来减少数据库的压力,同时通过业务标识符(biz_tag)区分不同的ID生成需求。
优点:
- 高性能:减少了数据库的读写频率,适合大规模ID生成。
- 高可用性:Leaf内置缓存机制,即使数据库短时间不可用,仍能继续提供ID。
缺点:
- 安全性低:ID的生成具有一定的规律性,可能暴露业务信息。
2. Leaf-snowflake方案
Leaf-snowflake方案沿用了雪花算法的设计,通过Zookeeper进行workerID的分配,适用于对ID安全性要求较高的场景。
优点:
- 随机性强:ID难以预测,适合订单等敏感业务。
- 弱依赖:即使Zookeeper出现问题,也能通过本地缓存继续生成ID。
缺点:
- 配置复杂:对于大型集群,Zookeeper的配置和维护成本较高。
具体代码可以参考如下
这个方法一般都通用实际代码需要考虑如下:
1. workerId (工作ID)
- 定义:
workerId用于标识同一个数据中心内的不同机器或节点。它可以唯一标识在一个数据中心内的每一台独立的工作节点(例如,每台服务器或每个应用实例)。
- 作用:在同一数据中心内,不同的机器会通过不同的
workerId来生成各自的ID,确保即使在同一毫秒内,也不会产生重复的ID。
- 位数:在
SnowflakeIdWorker实现中,workerId默认占用5位,所以它的值可以在0到31之间(2^5 = 32),意味着最多支持32个不同的工作节点。
2. datacenterId (数据中心ID)
- 定义:
datacenterId用于标识不同的数据中心或机房。它确保在不同的物理数据中心之间生成的ID也是唯一的。
- 作用:在大规模的分布式系统中,通常会有多个数据中心分布在不同的地理位置。
datacenterId能够区分这些数据中心,以便每个数据中心内部的机器生成的ID都能唯一且不冲突。
- 位数:同样,
datacenterId也占用5位,值可以在0到31之间,意味着最多支持32个不同的数据中心。
1. workerId 和 datacenterId 的配置
- 通用性:这段代码中,
workerId和datacenterId默认都设置为5位,允许分别表示32个不同的值(即支持32台机器,每台机器可以部署32个节点)。
- 调整需求:如果你的系统部署规模超过这个限制,比如你的数据中心超过32个,或者你在每个数据中心的机器数超过32,那么你需要调整
workerIdBits和datacenterIdBits的位数。这意味着,你需要重新计算这些字段的位数,最大支持的ID数,以及对应的移位操作。
- 示例:假设你有64个数据中心,你可能需要将
datacenterIdBits设为6位,而不是5位,这样会影响所有涉及位移和掩码的代码。
2. 时间戳的起始时间 (startTime)
- 通用性:代码中的时间戳是基于系统当前时间减去一个固定的起始时间戳来生成的。起始时间戳通常是系统上线时的某个固定时间。
- 调整需求:如果你在不同的项目中使用相同的代码,最好确保每个项目的起始时间戳一致,或者根据项目的部署时间来设置这个值。如果两个系统的起始时间戳不同,但你希望它们生成的ID在某些条件下互通或不冲突,那么你需要保证时间戳的同步性。
- 示例:可以通过配置文件来设置不同项目的起始时间戳,确保不同环境的时间同步。
3. 时钟同步与回拨问题
- 通用性:在高并发环境下,服务器的时钟可能会出现不同步或回拨的情况。代码中虽然有检测时钟回拨的逻辑,但在不同的项目中,如果你的分布式环境时钟同步性不好,可能会频繁遇到时钟回拨问题,导致ID生成失败。
- 调整需求:对于大规模分布式系统,你可能需要更严格的时钟同步机制(如NTP服务器的精确配置)或调整代码逻辑来处理更多的时钟回拨场景,甚至考虑增加对逻辑时钟的支持。
4. 序列号的设置 (sequenceBits)
- 通用性:当前的实现中,序列号占用12位,支持每毫秒生成4096个ID。
- 调整需求:如果你的应用在极高并发的环境下,可能在1毫秒内需要生成超过4096个ID,这时需要增加
sequenceBits的位数,但这会减少时间戳或机器ID的位数,你需要权衡和调整这些参数。
5. 线程安全与性能
- 通用性:
nextId()方法被设计为同步方法,以确保线程安全,这在单台机器高并发情况下可能成为性能瓶颈。
- 调整需求:对于高并发场景,你可以考虑通过锁分离、使用CAS操作或者无锁队列等方式优化性能,避免同步带来的性能问题。
- 作者:卷神
- 链接:https://blog.952712.xyz/article/19013107-3f0e-4425-a5b4-bd744ea9a6dd
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。






