Lisheng Xie - Blog - 分布式ID
https://www.lishengxie.top/index.php/tag/%E5%88%86%E5%B8%83%E5%BC%8FID/
-
分布式ID生成方案全解析:从数据库到雪花算法
https://www.lishengxie.top/index.php/archives/211/
2026-05-14T09:23:00+08:00
分布式ID生成方案全解析:从数据库到雪花算法在分布式系统中,全局唯一ID是一个常见的需求。无论是分库分表后的主键冲突,还是订单号、用户ID等需要唯一标识的场景,都离不开一个可靠的分布式ID生成器。本文将带你了解分布式ID的基本要求,并梳理多种实现方案,从简单的数据库自增到高性能的雪花算法,最后介绍各大厂的开源组件。一、什么是分布式ID?分布式ID是指在分布式环境下生成的全局唯一标识符。典型应用场景包括:分库分表:数据量增长导致单库单表出现性能瓶颈时,分库分表后无法继续依赖数据库自增ID(会产生主键冲突),此时需要分布式ID保证全局唯一。业务标识:订单号、用户ID、消息ID等需要全局唯一的场景。二、分布式ID的基本要求一个合格的分布式ID生成器通常需要满足以下几点:全局唯一:这是最基本的要求,ID必须在整个分布式系统中不重复。独立部署、高性能、高可用:ID生成服务应独立部署,具备高并发下的低延迟响应能力(接近100%可用性),不能成为业务系统的瓶颈。安全:ID中不应包含敏感信息,避免信息泄露。趋势递增:尽可能保证ID趋势递增,这有利于数据库的索引插入和排序性能。三、实现方案1. 数据库方案1.1 基于数据库自增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可反推出订单数量等敏感信息。1.2 基于数据库集群模式为解决单点故障,可以采用多主模式,部署多个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; -- 步长优点:缓解单点并发压力。缺点:不利于动态扩容,新增节点时需处理起始值冲突,甚至可能停机调整。1.3 基于数据库号段模式号段模式的核心思想是批量获取自增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开头),再在每个节点内部使用号段批量获取。此外,引入双缓存机制(预加载下一个号段)可进一步提升性能。2. 算法方案2.1 UUID(通用唯一识别码)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版本介绍2.2 雪花算法(Snowflake)Twitter开源的分布式ID生成算法,将64-bit(Java中用long类型存储)划分为多个部分:1 bit(未使用)41 bit(毫秒时间戳)10 bit(机器ID)12 bit(序列号) 理论上一台机器在一个毫秒内可生成4096个不重复ID,且趋势递增。优点:不依赖数据库等第三方系统,纯内存计算,性能极高;可根据业务需求灵活调整bit分配。缺点:强依赖机器时钟。若发生时钟回拨,可能产生重复ID或导致服务不可用(官方简单抛错处理,回拨期间服务暂停)。更详细的介绍参考:雪花算法解析2.3 薄雾算法一种替代方案,详情可参考:薄雾算法介绍3. 开源组件方案3.1 Redis利用Redis的INCR命令实现原子递增:127.0.0.1:6379> set seq_id 1 # 初始化
OK
127.0.0.1:6379> incr seq_id # 原子递增,返回新值
(integer) 2需要注意持久化配置和极端情况下的ID重复问题。3.2 Zookeeper基于Zookeeper的znode数据版本号生成序列号(32位或64位),客户端可用作唯一ID。由于高度依赖Zookeeper且为同步API调用,高并发场景下性能不理想。四、开源分布式ID组件推荐组件公司地址Tinyid滴滴https://github.com/didi/tinyidUidGenerator百度https://github.com/baidu/uid-generatorLeaf美团https://github.com/Meituan-Dianping/LeafSeqsvr腾讯https://github.com/uidgen/seqsvr延伸阅读:美团Leaf技术详解万亿级调用系统:微信序列号生成器架构设计及演变五、总结选择分布式ID方案时,需要根据业务场景权衡性能、可用性、有序性、复杂度等因素:如果并发量不高且可接受单点风险,数据库自增或号段模式足够简单实用;追求高性能且能接受时钟回拨的小概率风险,雪花算法是经典选择;希望开箱即用并具备生产级高可用,大厂开源的Leaf、Tinyid等组件值得考虑。没有完美的方案,只有最契合业务场景的选择。希望本文能帮助你理清分布式ID的设计思路,在实际项目中做出更合理的决策。