在分布式系统中,全局唯一ID是一个常见的需求。无论是分库分表后的主键冲突,还是订单号、用户ID等需要唯一标识的场景,都离不开一个可靠的分布式ID生成器。本文将带你了解分布式ID的基本要求,并梳理多种实现方案,从简单的数据库自增到高性能的雪花算法,最后介绍各大厂的开源组件。
分布式ID是指在分布式环境下生成的全局唯一标识符。典型应用场景包括:
一个合格的分布式ID生成器通常需要满足以下几点:

使用MySQL的自增主键来充当分布式ID。创建一张专门用于生成ID的表,每次插入一条记录后获取返回的主键ID。
CREATE DATABASE `SEQ_ID`;
CREATE TABLE SEQID.SEQUENCE_ID (
id bigint(20) unsigned NOT NULL auto_increment,
value char(10) NOT NULL default '',
PRIMARY KEY (id)
) ENGINE=MyISAM;
INSERT INTO SEQUENCE_ID(value) VALUES ('values');优点:简单、天然有序。
缺点:单点故障风险、并发性能差、数据库写入压力大,且ID可反推出订单数量等敏感信息。
为解决单点故障,可以采用多主模式,部署多个MySQL实例独立生成自增ID。通过设置不同的起始值和步长避免重复(以两个节点为例):
-- MySQL实例1
set @@auto_increment_offset = 1; -- 起始值
set @@auto_increment_increment = 2; -- 步长
-- MySQL实例2
set @@auto_increment_offset = 2; -- 起始值
set @@auto_increment_increment = 2; -- 步长优点:缓解单点并发压力。
缺点:不利于动态扩容,新增节点时需处理起始值冲突,甚至可能停机调整。
号段模式的核心思想是批量获取自增ID:每次从数据库取出一个ID范围(如(1,1000]),然后在内存中递增分配,用完后再去数据库获取新的号段。
表结构示例:
CREATE TABLE id_generator (
id int(10) NOT NULL,
max_id bigint(20) NOT NULL COMMENT '当前最大id',
step int(20) NOT NULL COMMENT '号段的步长',
biz_type int(20) NOT NULL COMMENT '业务类型',
version int(20) NOT NULL COMMENT '版本号',
PRIMARY KEY (`id`)
);
INSERT INTO `id_generator` (`id`, `max_id`, `step`, `version`, `biz_type`)
VALUES (1, 0, 100, 0, 101);获取号段的操作:
-- 查询当前号段
SELECT `max_id`, `step`, `version` FROM `id_generator` WHERE `biz_type` = 101;
-- 更新并获取新号段(乐观锁)
UPDATE id_generator
SET max_id = max_id + step, version = version + 1
WHERE version = 0 AND `biz_type` = 101;优点:相比每次获取都访问数据库,大幅减小数据库压力;即使数据库短暂故障,服务仍可使用已缓存的号段继续运行。
缺点:服务器重启或故障可能导致ID不连续。
优化扩展:可以将多主模式与号段模式结合,先为每个节点分配大区间(如节点1以10开头,节点2以11开头),再在每个节点内部使用号段批量获取。此外,引入双缓存机制(预加载下一个号段)可进一步提升性能。
Java中可通过UUID.randomUUID()生成,去掉连字符后得到一个32位随机字符串。
public static void main(String[] args) {
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
System.out.println(uuid);
}优点:实现简单、唯一性有保障。
缺点:不具备趋势递增(UUID v7版本已有所改善),导致数据库写入性能差、查询效率低;16字节的存储长度占用较大空间。
UUID的演进可以参考:UUID版本介绍
Twitter开源的分布式ID生成算法,将64-bit(Java中用long类型存储)划分为多个部分:
| 1 bit(未使用) | 41 bit(毫秒时间戳) | 10 bit(机器ID) | 12 bit(序列号) |
|---|---|---|---|
理论上一台机器在一个毫秒内可生成4096个不重复ID,且趋势递增。
优点:不依赖数据库等第三方系统,纯内存计算,性能极高;可根据业务需求灵活调整bit分配。
缺点:强依赖机器时钟。若发生时钟回拨,可能产生重复ID或导致服务不可用(官方简单抛错处理,回拨期间服务暂停)。
更详细的介绍参考:雪花算法解析
一种替代方案,详情可参考:薄雾算法介绍
利用Redis的INCR命令实现原子递增:
127.0.0.1:6379> set seq_id 1 # 初始化
OK
127.0.0.1:6379> incr seq_id # 原子递增,返回新值
(integer) 2需要注意持久化配置和极端情况下的ID重复问题。
基于Zookeeper的znode数据版本号生成序列号(32位或64位),客户端可用作唯一ID。由于高度依赖Zookeeper且为同步API调用,高并发场景下性能不理想。
| 组件 | 公司 | 地址 |
|---|---|---|
| Tinyid | 滴滴 | https://github.com/didi/tinyid |
| UidGenerator | 百度 | https://github.com/baidu/uid-generator |
| Leaf | 美团 | https://github.com/Meituan-Dianping/Leaf |
| Seqsvr | 腾讯 | https://github.com/uidgen/seqsvr |
延伸阅读:
选择分布式ID方案时,需要根据业务场景权衡性能、可用性、有序性、复杂度等因素:
没有完美的方案,只有最契合业务场景的选择。希望本文能帮助你理清分布式ID的设计思路,在实际项目中做出更合理的决策。
]]>