目录

1、分布式ID介绍

什么是 ID?

什么是分布式 ID?

分布式 ID 需要满足哪些要求?

2、分布式 ID 常见解决方案

1、数据库

示例使用2:

2、数据库号段模式

使用示例2:

一、核心设计思路

二、实现代码

1. 数据库表设计(MySQL)

2. 实体类(IdGeneratorDO)

3. Mapper 接口(IdGeneratorMapper)

4. Mapper XML 实现(IdGeneratorMapper.xml)

5. ID 生成器核心类(IdGenerator)

三、使用示例

3、NoSQL

使用示例2:

一、核心设计思路

二、实现代码

1. 依赖配置(Spring Boot)

2. Redis 分布式 ID 生成器实现

3. 使用示例

三、核心优势与注意事项

优势

注意事项

四、扩展优化

五、适用场景

4、算法

4.1、UUID

4.2、Snowflake(雪花算法)

4.3 开源框架

UidGenerator(百度)

Leaf(美团)

Tinyid(滴滴)

IdGenerator(个人)

总结

1、数据库

2、算法

3、开源框架



1、分布式ID介绍


什么是 ID?


日常开发中,我们需要对系统中的各种数据使用 ID 唯一表示,比如用户 ID 对应且仅对应一个人,商品 ID 对应且仅对应一件商品,订单 ID 对3应且仅对应一个订单。
我们现实生活中也有各种 ID,比如身份证 ID 对应且仅对应一个人、地址 ID 对应且仅对应一个地址。
简单来说,ID 就是数据的唯一标识。


什么是分布式 ID?


分布式 ID 是分布式系统下的 ID。分布式 ID 不存在与现实生活中,属于计算机系统中的一个概念。
我简单举一个分库分表的例子。
比如有一个项目,使用的是单机 MySQL 。但是,没想到的是,项目上线一个月之后,随着使用人数越来越多,整个系统的数据量将越来越大。单机 MySQL 已经没办法支撑了,需要进行分库分表(推荐 Sharding-JDBC)。
在分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?
这个时候就需要生成分布式 ID了。


分布式 ID 需要满足哪些要求?


分布式 ID 作为分布式系统中必不可少的一环,很多地方都要用到分布式 ID。
一个最基本的分布式 ID 需要满足下面这些要求:
        ●全局唯一:ID 的全局唯一性肯定是首先要满足的!
        ●高性能:分布式 ID 的生成速度要快,对本地资源消耗要小。
        ●高可用:生成分布式 ID 的服务要保证可用性无限接近于 100%。
        ●方便易用:拿来即用,使用方便,快速接入!

除了这些之外,一个比较好的分布式 ID 还应保证:
        ●安全:ID 中不包含敏感信息。
        ●有序递增:如果要把 ID 存放在数据库的话,ID 的有序性可以提升数据库写入速度。并且,很多时候 ,我们还很有可能会直接通过 ID 来进行排序。
        ●有具体的业务含义:生成的 ID 如果能有具体的业务含义,可以让定位问题以及开发更透明化(通过 ID 就能确定是哪个业务)。
        ●独立部署:也就是分布式系统单独有一个发号器服务,专门用来生成分布式 ID。这样就生成 ID 的服务可以和业务相关的服务解耦。不过,这样同样带来了网络调用消耗增加的问题。总的来说,如果需要用到分布式 ID 的场景比较多的话,独立部署的发号器服务还是很有必要的。

2、分布式 ID 常见解决方案

1、数据库


数据库主键自增


这种方式就比较简单直白了,就是通过关系型数据库的自增主键产生来唯一的 ID。


以 MySQL 举例,我们通过下面的方式即可。
1.创建一个数据库表。

CREATE TABLE `sequence_id` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`stub` char(10) NOT NULL DEFAULT '',PRIMARY KEY (`id`),UNIQUE KEY `stub` (`stub`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


stub 字段无意义,只是为了占位,便于我们插入或者修改数据。并且,给 stub 字段创建了唯一索引,保证其唯一性。
2.通过 replace into 来插入数据。


BEGIN;
REPLACE INTO sequence_id (stub) VALUES ('stub');
SELECT LAST_INSERT_ID();
COMMIT;


插入数据这里,我们没有使用 insert into 而是使用 replace into 来插入数据,具体步骤是这样的:
        ●第一步:尝试把数据插入到表中。
        ●第二步:如果主键或唯一索引字段出现重复数据错误而插入失败时,先从表中删除含有重复关键字值的冲突行,然后再次尝试把数据插入到表中。
这种方式的优缺点也比较明显:
        ●优点:实现起来比较简单、ID 有序递增、存储消耗空间小
        ●缺点:支持的并发量不大、存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )、每次获取 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;  -- 步长



这样两个MySQL实例的自增ID分别就是:

1、3、5、7、9
2、4、6、8、10
但是如果两个还是无法满足咋办呢?增加第三台MySQL实例需要人工修改一、二两台MySQL实例的起始值和步长,把第三台机器的ID起始生成位置设定在比现有最大自增ID的位置远一些,但必须在一、二两台MySQL实例ID还没有增长到第三台MySQL实例的起始ID值的时候,否则自增ID就要出现重复了,必要时可能还需要停机修改。

优点:解决DB单点问题
缺点:不利于后续扩容,而且实际上单个数据库自身压力还是大,依旧无法满足高并发场景。

示例使用2:

当我们需要一个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');


2、数据库号段模式


数据库主键自增这种模式,每次获取 ID 都要访问一次数据库,ID 需求比较大的时候,肯定是不行的。
如果我们可以批量获取,然后存在在内存里面,需要用到的时候,直接从内存里面拿就舒服了!这也就是我们说的 基于数据库的号段模式来生成分布式 ID
数据库的号段模式也是目前比较主流的一种分布式 ID 生成方式。像滴滴开源的Tinyid 就是基于这种方式来做的。不过,TinyId 使用了双号段缓存、增加多 db 支持等方式来进一步优化。
以 MySQL 举例,我们通过下面的方式即可。
1. 创建一个数据库表。


CREATE TABLE `sequence_id_generator` (`id` int(10) NOT NULL,`current_max_id` bigint(20) NOT NULL COMMENT '当前最大id',`step` int(10) NOT NULL COMMENT '号段的长度',`version` int(20) NOT NULL COMMENT '版本号',`biz_type`    int(20) NOT NULL COMMENT '业务类型',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


current_max_id 字段和step字段主要用于获取批量 ID,获取的批量 id 为:current_max_id ~ current_max_id+step。


version 字段主要用于解决并发问题(乐观锁),biz_type 主要用于表示业务类型。
2. 先插入一行数据。

INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
VALUES(1, 0, 100, 0, 101);


3. 通过 SELECT 获取指定业务下的批量唯一 ID


SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101


结果:


id current_max_id step version biz_type
1 0 100 0 101


4. 不够用的话,更新之后重新 SELECT 即可。

UPDATE sequence_id_generator SET current_max_id = 0+100, version=version+1 WHERE version = 0  AND `biz_type` = 101;
SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101


结果:

id current_max_id step version biz_type
1 100 100 1 101


相比于数据库主键自增的方式,数据库的号段模式对于数据库的访问次数更少,数据库压力更小。
另外,为了避免单点问题,你可以从使用主从模式来提高可用性。
数据库号段模式的优缺点:
        ●优点:ID 有序递增、存储消耗空间小
        ●缺点:存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )

使用示例2:

基于数据库的号段模式是生成分布式 ID 的常用方案,其核心思想是从数据库批量获取一段连续的 ID(号段),在本地内存中分配使用,减少数据库访问次数。以下是一个完整的实现示例:

一、核心设计思路
  1. 数据库表:存储每个业务的号段信息(当前最大 ID、号段步长)。
  2. 号段获取:当本地号段用尽时,从数据库申请下一段 ID(通过UPDATE加锁保证并发安全)。
  3. 本地分配:在内存中维护当前号段的起始 ID 和结束 ID,逐次递增分配,直到用尽后再次申请。
二、实现代码
1. 数据库表设计(MySQL)
CREATE TABLE `id_generator` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`biz_type` varchar(50) NOT NULL COMMENT '业务类型(如order、user)',`max_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '当前最大ID',`step` int(11) NOT NULL DEFAULT 1000 COMMENT '号段步长(每次申请的ID数量)',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_biz_type` (`biz_type`) COMMENT '业务类型唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '分布式ID生成器';-- 初始化业务号段(示例:订单业务)
INSERT INTO `id_generator` (`biz_type`, `max_id`, `step`) VALUES ('order', 0, 1000);
2. 实体类(IdGeneratorDO)
import lombok.Data;import java.util.Date;@Data
public class IdGeneratorDO {private Long id;private String bizType; // 业务类型private Long maxId;     // 当前最大IDprivate Integer step;   // 号段步长private Date updateTime; // 更新时间
}
3. Mapper 接口(IdGeneratorMapper)
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;public interface IdGeneratorMapper extends BaseMapper<IdGeneratorDO> {/*** 批量获取号段(核心方法:通过UPDATE加锁,保证并发安全)* @param bizType 业务类型* @param step 号段步长* @return 受影响的行数(1表示成功,0表示失败)*/int updateMaxId(@Param("bizType") String bizType, @Param("step") int step);/*** 获取当前业务的号段信息*/IdGeneratorDO selectByBizType(@Param("bizType") String bizType);
}
4. Mapper XML 实现(IdGeneratorMapper.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.IdGeneratorMapper"><!-- 更新max_id,获取下一个号段 --><update id="updateMaxId">UPDATE id_generatorSET max_id = max_id + #{step},update_time = NOW()WHERE biz_type = #{bizType}</update><!-- 查询业务对应的号段信息 --><select id="selectByBizType" resultType="com.example.entity.IdGeneratorDO">SELECT id, biz_type, max_id, step, update_timeFROM id_generatorWHERE biz_type = #{bizType}</select>
</mapper>
5. ID 生成器核心类(IdGenerator)
import cn.hutool.core.util.StrUtil;
import com.example.entity.IdGeneratorDO;
import com.example.mapper.IdGeneratorMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;/*** 基于数据库号段模式的分布式ID生成器*/
@Component
public class IdGenerator {@Autowiredprivate IdGeneratorMapper idGeneratorMapper;/*** 本地缓存:存储每个业务的当前号段信息* key: 业务类型(bizType)* value: 号段对象(Section)*/private final ConcurrentHashMap<String, Section> sectionCache = new ConcurrentHashMap<>();/*** 获取下一个ID* @param bizType 业务类型(如"order")* @return 分布式ID*/public Long nextId(String bizType) {if (StrUtil.isEmpty(bizType)) {throw new IllegalArgumentException("业务类型不能为空");}// 从本地缓存获取号段,若不存在或已用尽,则申请新号段Section section = sectionCache.get(bizType);if (section == null || section.is用尽()) {// 加锁防止并发申请号段ReentrantLock lock = new ReentrantLock();lock.lock();try {// 双重检查:避免重复申请section = sectionCache.get(bizType);if (section == null || section.is用尽()) {section = applyNewSection(bizType); // 从数据库申请新号段sectionCache.put(bizType, section);}} finally {lock.unlock();}}// 从当前号段获取下一个IDreturn section.nextId();}/*** 从数据库申请新号段*/private Section applyNewSection(String bizType) {// 1. 查询该业务的当前号段配置IdGeneratorDO config = idGeneratorMapper.selectByBizType(bizType);if (config == null) {throw new RuntimeException("业务类型[" + bizType + "]未配置号段信息");}int step = config.getStep();// 2. 申请新号段:通过UPDATE将max_id增加step(数据库层面加锁,保证并发安全)int updateRows = idGeneratorMapper.updateMaxId(bizType, step);if (updateRows != 1) {// 并发冲突时重试(最多重试3次)for (int i = 0; i < 3; i++) {updateRows = idGeneratorMapper.updateMaxId(bizType, step);if (updateRows == 1) {break;}try {Thread.sleep(10); // 短暂休眠后重试} catch (InterruptedException e) {Thread.currentThread().interrupt();}}if (updateRows != 1) {throw new RuntimeException("申请号段失败,业务类型:" + bizType);}}// 3. 重新查询最新的max_id(申请后的值)IdGeneratorDO newConfig = idGeneratorMapper.selectByBizType(bizType);long newMaxId = newConfig.getMaxId();// 号段范围:[newMaxId - step + 1, newMaxId]long startId = newMaxId - step + 1;long endId = newMaxId;return new Section(startId, endId);}/*** 号段内部类:维护当前号段的起始ID、结束ID和当前ID*/private static class Section {private final long startId; // 号段起始IDprivate final long endId;   // 号段结束IDprivate long currentId;     // 当前分配到的IDpublic Section(long startId, long endId) {this.startId = startId;this.endId = endId;this.currentId = startId - 1; // 初始化为起始ID的前一个}/*** 获取下一个ID*/public synchronized Long nextId() {if (is用尽()) {throw new RuntimeException("号段已用尽,startId=" + startId + ", endId=" + endId);}currentId++;return currentId;}/*** 判断号段是否用尽*/public boolean is用尽() {return currentId >= endId;}}
}
三、使用示例
import com.example.service.IdGenerator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest
@RunWith(SpringRunner.class)
public class IdGeneratorTest {@Autowiredprivate IdGenerator idGenerator;@Testpublic void testNextId() {// 生成订单业务的IDfor (int i = 0; i < 5; i++) {Long orderId = idGenerator.nextId("order");System.out.println("生成订单ID:" + orderId);}}
}

输出结果(首次运行):

生成订单ID:1
生成订单ID:2
生成订单ID:3
生成订单ID:4
生成订单ID:5

3、NoSQL


一般情况下,NoSQL 方案使用 Redis 多一些。我们通过 Redis 的 incr 命令即可实现对 id 原子顺序递增。


127.0.0.1:6379> set sequence_id_biz_type 1
OK
127.0.0.1:6379> incr sequence_id_biz_type
(integer) 2
127.0.0.1:6379> get sequence_id_biz_type
"2"


为了提高可用性和并发,我们可以使用 Redis Cluster。Redis Cluster 是 Redis 官方提供的 Redis 集群解决方案(3.0+版本)。
Redis 方案的优缺点:
        ●优点:性能不错并且生成的 ID 是有序递增的
        ●缺点:和数据库主键自增方案的缺点类似

注意:

        用redis实现需要注意一点,要考虑到redis持久化的问题。redis有两种持久化方式RDBAOF

  • RDB会定时打一个快照进行持久化,假如连续自增但redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。
  • AOF会对每条写命令进行持久化,即使Redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致Redis重启恢复的数据时间过长。


除了 Redis 之外,MongoDB ObjectId 经常也会被拿来当做分布式 ID 的解决方案。


MongoDB ObjectId 一共需要 12 个字节存储:
        ●0~3:时间戳
        ●3~6:代表机器 ID
        ●7~8:机器进程 ID
        ●9~11:自增值
MongoDB 方案的优缺点:
        ●优点:性能不错并且生成的 ID 是有序递增的
        ●缺点:需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)、有安全性问题(ID 生成有规律性)

使用示例2:

基于 Redis 生成分布式 ID 是一种高性能方案,利用 Redis 的原子操作(如INCR)保证 ID 的唯一性和递增性。以下是完整的实现示例:

一、核心设计思路
  1. 原子递增:使用 Redis 的INCR命令对每个业务类型的 ID 键进行原子递增,确保分布式环境下 ID 唯一。
  2. 前缀区分业务:为不同业务(如订单、用户)设置独立的 Redis 键(如id:orderid:user),避免 ID 冲突。
  3. ID 结构优化:可组合 “时间戳 + 自增数 + 机器标识” 等信息,使 ID 具备趋势递增性和可追溯性。
  4. 缓存与容错:本地缓存 Redis 生成的 ID 段,减少 Redis 访问次数;当 Redis 不可用时,使用备用方案避免服务中断。
二、实现代码
1. 依赖配置(Spring Boot)

pom.xml 引入 Redis 依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml 配置 Redis:

spring:redis:host: localhostport: 6379password:  # 若有密码请填写database: 0timeout: 3000mslettuce:pool:max-active: 16  # 连接池最大连接数max-idle: 8     # 连接池最大空闲连接数
2. Redis 分布式 ID 生成器实现
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;@Component
public class RedisIdGenerator {private final RedisTemplate<String, Long> redisTemplate;// Redis键前缀(区分不同业务)private static final String ID_KEY_PREFIX = "distributed:id:";// 本地缓存的ID段大小(减少Redis访问次数)private static final int BATCH_SIZE = 1000;// 本地缓存:key=业务类型,value=ID段信息private final LocalIdCache localCache = new LocalIdCache();public RedisIdGenerator(RedisTemplate<String, Long> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 生成分布式ID(带业务前缀)* @param bizType 业务类型(如"order"、"user")* @return 分布式ID*/public Long generateId(String bizType) {// 从本地缓存获取ID,若缓存不足则从Redis批量获取IdSegment segment = localCache.getSegment(bizType);if (segment == null || segment.isExhausted()) {ReentrantLock lock = new ReentrantLock();lock.lock();try {// 双重检查,避免并发重复申请segment = localCache.getSegment(bizType);if (segment == null || segment.isExhausted()) {// 从Redis申请新的ID段segment = fetchIdSegmentFromRedis(bizType);localCache.setSegment(bizType, segment);}} finally {lock.unlock();}}return segment.nextId();}/*** 从Redis批量获取ID段(原子操作)*/private IdSegment fetchIdSegmentFromRedis(String bizType) {String redisKey = ID_KEY_PREFIX + bizType;// 原子递增,一次获取BATCH_SIZE个ID(返回递增后的结果)Long end = redisTemplate.opsForValue().increment(redisKey, BATCH_SIZE);if (end == null) {throw new RuntimeException("Redis生成ID失败,key=" + redisKey);}// ID段范围:[end - BATCH_SIZE + 1, end]long start = end - BATCH_SIZE + 1;return new IdSegment(start, end);}/*** 生成带时间戳的复合ID(如:1717234567890 + 12345 → 171723456789012345)* 适用于需要ID包含时间信息的场景*/public Long generateTimestampId(String bizType) {long timestamp = System.currentTimeMillis(); // 时间戳(毫秒级)long sequence = generateId(bizType) % 100000; // 取5位自增数(避免ID过长)// 组合:时间戳(13位) + 自增数(5位) → 18位IDreturn timestamp * 100000 + sequence;}/*** 本地ID段缓存:减少Redis访问次数*/private static class LocalIdCache {private final java.util.Map<String, IdSegment> cache = new java.util.HashMap<>();public IdSegment getSegment(String bizType) {return cache.get(bizType);}public void setSegment(String bizType, IdSegment segment) {cache.put(bizType, segment);}}/*** ID段内部类:维护批量获取的ID范围*/private static class IdSegment {private final long start; // 起始IDprivate final long end;   // 结束IDprivate long current;     // 当前分配到的IDpublic IdSegment(long start, long end) {this.start = start;this.end = end;this.current = start - 1; // 初始化为起始ID的前一个}/*** 获取下一个ID*/public synchronized long nextId() {if (isExhausted()) {throw new RuntimeException("ID段已用尽,start=" + start + ", end=" + end);}return ++current;}/*** 判断ID段是否用尽*/public boolean isExhausted() {return current >= end;}}
}
3. 使用示例
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisIdGeneratorTest {@Autowiredprivate RedisIdGenerator idGenerator;@Testpublic void testGenerateId() {// 生成订单业务IDfor (int i = 0; i < 5; i++) {Long orderId = idGenerator.generateId("order");System.out.println("订单ID:" + orderId);}// 生成带时间戳的用户IDfor (int i = 0; i < 5; i++) {Long userId = idGenerator.generateTimestampId("user");System.out.println("用户ID(带时间戳):" + userId);}}
}

输出结果

订单ID:1
订单ID:2
订单ID:3
订单ID:4
订单ID:5
用户ID(带时间戳):171723456789000001
用户ID(带时间戳):171723456789000002
用户ID(带时间戳):171723456789000003
用户ID(带时间戳):171723456789000004
用户ID(带时间戳):171723456789000005
三、核心优势与注意事项
优势
  1. 高性能:Redis 单线程原子操作 + 本地缓存,支持高并发(每秒可达 10 万 + ID 生成)。
  2. 唯一性INCR命令保证分布式环境下 ID 绝对唯一,无重复风险。
  3. 趋势递增:ID 单调递增,适合数据库索引优化(B + 树索引对有序数据更友好)。
  4. 轻量易用:无需复杂的数据库表设计,通过 Redis 命令即可实现。
  5. 可扩展性:支持多业务类型隔离,通过键前缀区分不同场景。
注意事项
  1. Redis 可用性:依赖 Redis 服务,需确保 Redis 高可用(如主从 + 哨兵 / 集群模式)。
  2. ID 连续性:服务重启后,本地缓存的 ID 段会丢失,可能导致 ID 不连续(但不影响唯一性)。
  3. 步长设置BATCH_SIZE(批量获取的 ID 数量)需根据业务并发调整:
    • 高并发场景设大(如 10000),减少 Redis 访问次数;
    • 低并发场景设小(如 100),减少 ID 浪费。
  4. 数据持久化:Redis 需开启 RDB/AOF 持久化,避免重启后 ID 从 0 开始导致重复。
  5. ID 长度控制:复合 ID(如时间戳 + 自增数)需注意长度,避免超出数据库字段限制(如 MySQL 的 BIGINT 最大支持 19 位)。
四、扩展优化
  1. 容错降级:当 Redis 不可用时,切换到本地自增 ID(需保证机器标识唯一,避免分布式重复)。

    // 简化示例:本地降级方案
    private Long fallbackGenerateId(String bizType) {// 机器标识(如IP后两位)+ 时间戳 + 本地自增数String machineId = "10"; // 实际应动态获取long timestamp = System.currentTimeMillis() / 1000; // 秒级时间戳long localSeq = localSequence.getAndIncrement();return Long.parseLong(machineId + timestamp + localSeq);
    }
    
  2. 过期清理:为 Redis 键设置过期时间(如 1 年),避免长期积累无效键。

    // 生成ID时同步设置过期时间
    redisTemplate.expire(redisKey, 365, TimeUnit.DAYS);
    
  3. 预加载 ID 段:当本地 ID 段剩余不足 20% 时,异步提前从 Redis 获取新号段,避免生成 ID 时的阻塞。

  4. 自定义 ID 格式:根据业务需求扩展 ID 结构,例如:

    • 多机房场景:机房ID(2位)+ 时间戳(13位)+ 自增数(4位)
    • 分库分表场景:表标识(3位)+ 自增数(16位)
五、适用场景
  • 高并发业务(如秒杀、电商订单):依赖 Redis 的高性能。
  • 需要 ID 趋势递增的场景:如数据库分库分表、日志排序。
  • 跨服务 ID 生成:如分布式系统中多个微服务共享 ID 生成器。

通过 Redis 生成分布式 ID,既能保证唯一性和性能,又能灵活扩展 ID 格式,是分布式系统的优选方案之一。


4、算法


4.1、UUID


UUID 是 Universally Unique Identifier(通用唯一标识符) 的缩写。UUID 包含 32 个 16 进制数字(8-4-4-4-12)。
JDK 就提供了现成的生成 UUID 的方法,一行代码就行了。

//输出示例:cb4a9edefa5e4585b9bbd60bce986eaa
UUID.randomUUID().tostring().replaceA11("-","");


RFC 4122 中关于 UUID 的示例是这样的:


我们这里重点关注一下这个 Version(版本),不同的版本对应的 UUID 的生成规则是不同的。
5 种不同的 Version(版本)值分别对应的含义(参考维基百科对于 UUID 的介绍):
        ●版本 1 : UUID 是根据时间和节点 ID(通常是 MAC 地址)生成;
        ●版本 2 : UUID 是根据标识符(通常是组或用户 ID)、时间和节点 ID 生成;
        ●版本 3、版本 5 : 版本 5 - 确定性 UUID 通过散列(hashing)名字空间(namespace)标识符和名称生成;
        ●版本 4 : UUID 使用随机性或伪随机性生成。
下面是 Version 1 版本下生成的 UUID 的示例:


JDK 中通过 UUID 的 randomUUID() 方法生成的 UUID 的版本默认为 4。


UUID uuid = UUID.randomUUID();
int version = uuid.version();// 4


从上面的介绍中可以看出,UUID 可以保证唯一性,因为其生成规则包括 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,计算机基于这些规则生成的 UUID 是肯定不会重复的。
虽然,UUID 可以做到全局唯一性,但是,我们一般很少会使用它。
比如使用 UUID 作为 MySQL 数据库主键的时候就非常不合适:
        ●数据库主键要尽量越短越好,而 UUID 的消耗的存储空间比较大(32 个字符串,128 位)。
        ●UUID 是无顺序的,InnoDB 引擎下,数据库主键的无序性会严重影响数据库性能。
最后,我们再简单分析一下 UUID 的优缺点
        ●优点:生成足够简单,本地生成无网络消耗,具有唯一性
        ●缺点:存储消耗空间大(32 个字符串,128 位)、 不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)


4.2、Snowflake(雪花算法)


Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:


        ●sign(1bit):符号位(标识正负),始终为 0,代表生成的 ID 为正数。
        ●timestamp (41 bits):一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
        ●datacenter id + worker id (10 bits):一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
        ●sequence (12 bits):一共 12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID。
在实际项目中,我们一般也会对 Snowflake 算法进行改造,最常见的就是在 Snowflake 算法生成的 ID 中加入业务类型信息。
我们再来看看 Snowflake 算法的优缺点:
        ●优点:生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)
        ●缺点:需要解决重复 ID 问题(ID 生成依赖时间,在获取时间的时候,可能会出现时间回拨的问题,也就是服务器上的时间突然倒退到之前的时间,进而导致会产生重复 ID)、依赖机器 ID 对分布式环境不友好(当需要自动启停或增减机器时,固定的机器 ID 可能不够灵活)。


4.3 开源框架

UidGenerator(百度)


UidGenerator 是百度开源的一款基于 Snowflake(雪花算法)的唯一 ID 生成器。
不过,UidGenerator 对 Snowflake(雪花算法)进行了改进,生成的唯一 ID 组成如下:


        ●sign(1bit):符号位(标识正负),始终为 0,代表生成的 ID 为正数。
        ●delta seconds (28 bits):当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约 8.7 年
        ●worker id (22 bits):机器 id,最多可支持约 420w 次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
        ●sequence (13 bits):每秒下的并发序列,13 bits 可支持每秒 8192 个并发。
可以看出,和原始 Snowflake(雪花算法)生成的唯一 ID 的组成不太一样。并且,上面这些参数我们都可以自定义。
UidGenerator 官方文档中的介绍如下:


自 18 年后,UidGenerator 就基本没有再维护了

Leaf(美团)


Leaf 是美团开源的一个分布式 ID 解决方案 。这个项目的名字 Leaf(树叶) 起源于德国哲学家、数学家莱布尼茨的一句话:“There are no two identical leaves in the world”(世界上没有两片相同的树叶) 。这名字起得真心挺不错的,有点文艺青年那味了!
Leaf 提供了 号段模式 和 Snowflake(雪花算法) 这两种模式来生成分布式 ID。并且,它支持双号段,还解决了雪花 ID 系统时钟回拨问题。不过,时钟问题的解决需要弱依赖于 Zookeeper(使用 Zookeeper 作为注册中心,通过在特定路径下读取和创建子节点来管理 workId) 。
Leaf 的诞生主要是为了解决美团各个业务线生成分布式 ID 的方法多种多样以及不可靠的问题。
Leaf 对原有的号段模式进行改进,比如它这里增加了双号段避免获取 DB 在获取号段的时候阻塞请求获取 ID 的线程。简单来说,就是我一个号段还没用完之前,我自己就主动提前去获取下一个号段


根据项目 README 介绍,在 4C8G VM 基础上,通过公司 RPC 方式调用,QPS 压测结果近 5w/s,TP999 1ms。

Tinyid(滴滴)


Tinyid 是滴滴开源的一款基于数据库号段模式的唯一 ID 生成器。
数据库号段模式的原理我们在上面已经介绍过了。Tinyid 有哪些亮点呢?
为了搞清楚这个问题,我们先来看看基于数据库号段模式的简单架构方案。(图片来自于 Tinyid 的官方 wiki:《Tinyid 原理介绍》)


在这种架构模式下,我们通过 HTTP 请求向发号器服务申请唯一 ID。负载均衡 router 会把我们的请求送往其中的一台 tinyid-server。
这种方案有什么问题呢?在我看来(Tinyid 官方 wiki 也有介绍到),主要由下面这 2 个问题:
        ●获取新号段的情况下,程序获取唯一 ID 的速度比较慢。
        ●需要保证 DB 高可用,这个是比较麻烦且耗费资源的。
除此之外,HTTP 调用也存在网络开销。
Tinyid 的原理比较简单,其架构如下图所示:


相比于基于数据库号段模式的简单架构方案,Tinyid 方案主要做了下面这些优化:
        ●双号段缓存:为了避免在获取新号段的情况下,程序获取唯一 ID 的速度比较慢。 Tinyid 中的号段在用到一定程度的时候,就会去异步加载下一个号段,保证内存中始终有可用号段。
        ●增加多 db 支持:支持多个 DB,并且,每个 DB 都能生成唯一 ID,提高了可用性。
        ●增加 tinyid-client:纯本地操作,无 HTTP 请求消耗,性能和可用性都有很大提升。
Tinyid 的优缺点这里就不分析了,结合数据库号段模式的优缺点和 Tinyid 的原理就能知道。
 

IdGenerator(个人)


和 UidGenerator、Leaf 一样,IdGenerator 也是一款基于 Snowflake(雪花算法)的唯一 ID 生成器。IdGenerator 有如下特点:
        ●生成的唯一 ID 更短;
        ●兼容所有雪花算法(号段模式或经典模式,大厂或小厂);
        ●原生支持 C#/Java/Go/C/Rust/Python/Node.js/PHP(C 扩展)/SQL/ 等语言,并提供多线程安全调用动态库(FFI);
        ●解决了时间回拨问题,支持手工插入新 ID(当业务需要在历史时间生成新 ID 时,用本算法的预留位能生成 5000 个每秒);
        ●不依赖外部存储系统;
        ●默认配置下,ID 可用 71000 年不重复。
IdGenerator 生成的唯一 ID 组成如下:


        ●timestamp (位数不固定):时间差,是生成 ID 时的系统时间减去 BaseTime(基础时间,也称基点时间、原点时间、纪元时间,默认值为 2020 年) 的总时间差(毫秒单位)。初始为 5bits,随着运行时间而增加。如果觉得默认值太老,你可以重新设置,不过要注意,这个值以后最好不变。
        ●worker id (默认 6 bits):机器 id,机器码,最重要参数,是区分不同机器或不同应用的唯一 ID,最大值由 WorkerIdBitLength(默认 6)限定。如果一台服务器部署多个独立服务,需要为每个服务指定不同的 WorkerId。
        ●sequence (默认 6 bits):序列数,是每毫秒下的序列数,由参数中的 SeqBitLength(默认 6)限定。增加 SeqBitLength 会让性能更高,但生成的 ID 也会更长。
Java 语言使用示例:https://github.com/yitter/idgenerator/tree/master/Java。


总结


1、数据库


        a、主键自增
                ■优点:实现起来比较简单、ID 有序递增、存储消耗空间小。
                ■缺点:支持的并发量不大、存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )、每次获取 ID 都要访问一次数据库(增加了对数据库的压力,获取速度也慢)
        b、号段模式
                ■优点:ID 有序递增、存储消耗空间小
                ■缺点:存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊!)
        c、NoSQL,比如Redis
                ■优点:性能不错并且生成的 ID 是有序递增的
                ■缺点:和数据库主键自增方案的缺点类似


2、算法


        a、UUID
                ■优点:生成速度比较快、简单易用
                ■缺点:存储消耗空间大(32 个字符串,128 位)、 不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)
        b、Snowflake(雪花算法)
                ■优点:生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)
                ■缺点:需要解决重复 ID 问题(ID 生成依赖时间,在获取时间的时候,可能会出现时间回拨的问题,也就是服务器上的时间突然倒退到之前的时间,进而导致会产生重复 ID)、依赖机器 ID 对分布式环境不友好(当需要自动启停或增减机器时,固定的机器 ID 可能不够灵活)。

3、开源框架


        a、UidGenerator(百度):基于Snowflake(雪花算法)做了改进
        b、Leaf(美团): 支持号段模式 和 Snowflake(雪花算法) 来生成分布式 ID,增加了双号段避免获取 DB 在获取号段的时候阻塞请求获取 ID 的线程
        c、Tinyid(滴滴)
                ■双号段缓存:为了避免在获取新号段的情况下,程序获取唯一 ID 的速度比较慢。 Tinyid 中的号段在用到一定程度的时候,就会去异步加载下一个号段,保证内存中始终有可用号段。
                ■增加多 db 支持:支持多个 DB,并且,每个 DB 都能生成唯一 ID,提高了可用性。
                ■增加 tinyid-client:纯本地操作,无 HTTP 请求消耗,性能和可用性都有很大提升。
        d、IdGenerator(个人)
                ■生成的唯一 ID 更短;
                ■兼容所有雪花算法(号段模式或经典模式,大厂或小厂);
                ■原生支持 C#/Java/Go/C/Rust/Python/Node.js/PHP(C 扩展)/SQL/ 等语言,并提供多线程安全调用动态库(FFI);
                ■解决了时间回拨问题,支持手工插入新 ID(当业务需要在历史时间生成新 ID 时,用本算法的预留位能生成 5000 个每秒);
                ■不依赖外部存储系统;
                ■默认配置下,ID 可用 71000 年不重复。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91068.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91068.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/91068.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Rust 实战三 | HTTP 服务开发及 Web 框架推荐

往期回顾 Rust 实战二 | 开发简易版命令行工具 grepRust 实战一 | 用 RustRover 开发猜数字游戏Rust 安装与版本更新 代码开源地址&#xff1a;https://github.com/0604hx/rust-journey &#x1f680; Web 框架 名称性能&#xff08;QPS&#xff09;WebSocket / SSEGitHub ⭐…

墨者:SQL过滤字符后手工注入漏洞测试(第1题)

1. 墨者学院&#xff1a;SQL过滤字符后手工注入漏洞测试(第1题)&#x1f680; 2. 漏洞背景与测试思路&#x1f50d; 在Web安全测试中&#xff0c;当遇到对输入字符有过滤的SQL注入漏洞时&#xff0c;我们需要使用特殊技巧绕过过滤机制。本次测试的目标URL存在字符过滤&#xff…

深入解析 Vue 3 中 v-model 与表单元素的绑定机制

v-model 是 Vue 中最强大的指令之一&#xff0c;它简化了表单数据双向绑定的实现。本文将系统梳理各种 HTML 表单元素与 v-model 的绑定关系&#xff0c;特别是那些容易引起困惑的类型。一、v-model 的本质v-model 是一个语法糖&#xff0c;它实际上是 :value 和 input 的组合&…

【赵渝强老师】MySQL中的数据库对象

MySQL数据库中包含各自数据库对象&#xff0c;常见的数据库对象有&#xff1a;表、索引、视图、事件、存储过程和存储函数等等。 视频讲解如下 【赵渝强老师】MySQL中的数据库对象一、 创建与管理表 表是一种非常重要的数据库对象&#xff0c;MySQL数据库的数据都是存储在表中…

Angular面试题目和答案大全

基础概念篇1. 什么是Angular&#xff1f;它与AngularJS有什么区别&#xff1f;答案&#xff1a; Angular是由Google开发的基于TypeScript的开源Web应用框架&#xff0c;用于构建单页应用程序&#xff08;SPA&#xff09;。Angular vs AngularJS对比&#xff1a;特性AngularJSAn…

CSS 语音参考

CSS 语音参考 概述 CSS&#xff08;层叠样式表&#xff09;是用于描述HTML或XML文档样式的样式表语言。它为网页元素提供了一种统一的方式来定义字体、颜色、布局和其他视觉属性。CSS语音参考旨在为开发者提供一个详尽的指南&#xff0c;以便他们能够更有效地使用CSS来增强网页…

C# WPF 实现读取文件夹中的PDF并显示其页数

文章目录技术选型第一步&#xff1a;创建项目并安装依赖库第二步&#xff1a;定义数据模型 (Model)第三步&#xff1a;创建视图模型 (ViewModel)第四步&#xff1a;设计用户界面 (View)总结与解释后记关于转换器的错误工作中需要整理一些PDF格式文件&#xff0c;程序员的存在就…

设计模式(五)创建型:原型模式详解

设计模式&#xff08;五&#xff09;创建型&#xff1a;原型模式详解原型模式&#xff08;Prototype Pattern&#xff09;是 GoF 23 种设计模式中的创建型模式之一&#xff0c;其核心价值在于通过复制现有对象来创建新对象&#xff0c;而不是通过 new 关键字调用构造函数。它特…

K8S 八 数据存储-高级存储PV PVC 生命周期;配置存储ConfigMap Secret

目录数据存储 Volume8.1 基本存储8.1.1 EmptyDir8.1.2 HostPath 挂载目录8.1.3 NFSnfs的服务8.2 高级存储8.2.1 PV和PVC8.2.2 PV 持久化卷申请8.2.3 PVC 资源申请PVC的配置参数8.2.4 生命周期配置存储8.3.1 ConfigMap8.3.2 Secret数据存储 Volume Kubernetes的Volume支持多种类…

Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现轮船检测识别(C#代码UI界面版)

Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现轮船检测识别&#xff08;C#代码UI界面版&#xff09;工业相机使用YoloV8模型实现轮船检测识别工业相机通过YoloV8模型实现轮船检测识别的技术背景在相机SDK中获取图像转换图像的代码分析工业相机图像转换Bitmap图像格…

自习室预约小程序的设计与实现

自习室预约小程序的设计与实现现代学习环境对高效、便捷的预约系统需求日益增长。自习室预约小程序结合前沿技术栈&#xff0c;提供流畅的用户体验和强大的后台管理功能&#xff0c;满足学生、职场人士等群体的自习需求。技术架构与核心功能Vue.js 构建动态前端界面 采用 Vue.j…

Docker 实战大纲

文章目录Docker 实战 – Mysql &#xff08;敬请期待……&#xff09;

从一个“诡异“的C++程序理解状态机、防抖与系统交互

引言 在编程世界中&#xff0c;有时一个看似简单的代码片段可能隐藏着令人惊讶的复杂性。本文将从一个"故意设计"的C程序出发&#xff0c;深入探讨其背后涉及的状态机模式、防抖机制以及操作系统与控制台的交互原理。通过这个案例&#xff0c;我们不仅能理解这些核心…

NAS-Bench-101: Towards Reproducible Neural Architecture Search

概述这篇题为"NAS-Bench-101: Towards Reproducible Neural Architecture Search"的论文由Chris Ying等人合作完成&#xff0c;旨在解决神经网络架构搜索(NAS)领域面临的重大挑战&#xff1a;计算资源需求高和实验难以复现的问题。论文提出了NAS-Bench-101&#xff0…

SpringBoot整合Fastexcel/EasyExcel导出Excel导出多个图片

整个工具的代码都在Gitee或者Github地址内 gitee&#xff1a;solomon-parent: 这个项目主要是总结了工作上遇到的问题以及学习一些框架用于整合例如:rabbitMq、reids、Mqtt、S3协议的文件服务器、mongodb、xxl-job、powerjob还有用Docker compose部署各类中间组件。如果大家有…

网络原理--HTTPHTTPS

目录 一、HTTP 1.1 HTTP是什么 1.2 HTTP协议的工作过程 1.3 HTTP协议格式 1.3.1 抓包工具的使用 1.3.2 抓包结果 1.4 HTTP请求 1.4.1 URL 1.4.2 认识“方法” (method) 1.4.3 认识请求“报头”(header) 1.4.4 认识请求“正文”(body) 1.5 HTTP 响应详解 1.5.1 HTTP…

『 C++ 入门到放弃 』- 哈希表

一、哈希的概念 哈希&#xff0c;也称「 散列 」是一种用来进行高效查找的数据结构&#xff0c;查找的时间复杂度平均为O(1)&#xff0c;其本质就是依赖哈希函数这个算法来将 key 和该 key 存储位置建立一个映射关系。 而因为是有着映射关系&#xff0c;所以哈希的事件复杂度为…

零售收银系统开源代码全解析:连锁门店一体化解决方案(含POS+进销存+商城)

过去10年&#xff0c;收银系统技术经历了从单机版到云服务、从单纯结算到全渠道整合的快速演进。面对连锁多门店、AI称重、智能分账、跨店库存同步等新需求&#xff0c;很多企业的现有传统saas系统已显乏力。本文将梳理收银系统关键技术指标&#xff0c;助您在系统升级时做出明…

能源高效利用如何实现?楼宇自控系统智能化监管建筑设备

随着全球能源危机日益严峻和“双碳”目标的持续推进&#xff0c;建筑领域作为能耗大户&#xff08;约占社会总能耗的40%&#xff09;&#xff0c;其节能潜力备受关注。楼宇自控系统&#xff08;Building Automation System&#xff0c;简称BAS&#xff09;作为建筑智能化的核心…

校园二手交易小程序的设计与实现

文章目录前言详细视频演示具体实现截图后端框架SpringBoot微信小程序持久层框架MyBaits成功系统案例&#xff1a;参考代码数据库源码获取前言 博主介绍:CSDN特邀作者、985高校计算机专业毕业、现任某互联网大厂高级全栈开发工程师、Gitee/掘金/华为云/阿里云/GitHub等平台持续…