标签搜索

分布式ID生成方案全解析:从数据库到雪花算法

lishengxie
2026-05-14 / 0 评论 / 1 阅读 / 正在检测是否收录...

分布式ID生成方案全解析:从数据库到雪花算法

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

一、什么是分布式ID?

分布式ID是指在分布式环境下生成的全局唯一标识符。典型应用场景包括:

  • 分库分表:数据量增长导致单库单表出现性能瓶颈时,分库分表后无法继续依赖数据库自增ID(会产生主键冲突),此时需要分布式ID保证全局唯一。
  • 业务标识:订单号、用户ID、消息ID等需要全局唯一的场景。

二、分布式ID的基本要求

一个合格的分布式ID生成器通常需要满足以下几点:

  1. 全局唯一:这是最基本的要求,ID必须在整个分布式系统中不重复。
  2. 独立部署、高性能、高可用:ID生成服务应独立部署,具备高并发下的低延迟响应能力(接近100%可用性),不能成为业务系统的瓶颈。
  3. 安全:ID中不应包含敏感信息,避免信息泄露。
  4. 趋势递增:尽可能保证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/tinyid
UidGenerator百度https://github.com/baidu/uid-generator
Leaf美团https://github.com/Meituan-Dianping/Leaf
Seqsvr腾讯https://github.com/uidgen/seqsvr

延伸阅读

五、总结

选择分布式ID方案时,需要根据业务场景权衡性能、可用性、有序性、复杂度等因素:

  • 如果并发量不高且可接受单点风险,数据库自增或号段模式足够简单实用;
  • 追求高性能且能接受时钟回拨的小概率风险,雪花算法是经典选择;
  • 希望开箱即用并具备生产级高可用,大厂开源的Leaf、Tinyid等组件值得考虑。

没有完美的方案,只有最契合业务场景的选择。希望本文能帮助你理清分布式ID的设计思路,在实际项目中做出更合理的决策。

0

评论 (0)

取消