分布式ID(或叫全局唯一ID)的实现
分布式ID特性
- 全局唯一性:保证在整个分布式系统中唯一性,不会出现重复的ID。
- 高可用性:可以通过水平扩展、冗余备份或集群部署来确保。即使某个节点或组件发生故障,仍然能够正常。
- 安全性:分布式ID生成器独立于 业务逻辑的。设计为一个单独的组件或服务,可以被各种服务共享使用。
- 高性能:要求在很短的时间内生成唯一的标识符。
- 递增性:可按时间顺序排序,以便ID进行索引。
分布式ID实现
时间戳+计数器(每天一个key,方便统计当日订单)
- 符号位:1bit,永远为0(表示正数)
- 时间戳:31bit,以秒为单位,可以使用69年(\(2^{31}/3600/24/365≈69\))
- 序列号:32bit,秒内的计数器,支持每秒产生\(2^{32}\)个不同ID
序列号生成的解释
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
long count = stringRedisTemplate.opsForValue().increment("inc:" + keyPrefix + ":" + date);
- 格式化当前日期:
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
-
now对象: 这是一个LocalDateTime类型的实例,表示当前的日期和时间。 -
DateTimeFormatter.ofPattern("yyyy:MM:dd"):创建了一个日期时间格式化器,指定了格式模式为 "yyyy:MM:dd"。在这个模式中:yyyy表示四位数的年份。MM表示两位数的月份。dd表示两位数的日期。- 冒号
:作为日期部分的分隔符。
-
now.format(...): 将当前的LocalDateTime按照指定的格式转换为字符串。例如,如果当前日期是 2025 年 4 月 9 日,那么date的值将是"2025:04:09"。
- 在 Redis 中递增特定键的值:
long count = stringRedisTemplate.opsForValue().increment("inc:" + keyPrefix + ":" + date);
- 键的构造: 通过字符串拼接,生成了一个特定格式的键名:
"inc:" + keyPrefix + ":" + date。"inc:"是固定的前缀,表示这是一个计数器(increment)的键。keyPrefix是传入的方法参数,用于区分不同的业务场景或计数类别。date是上一步生成的日期字符串,确保计数器是按天分隔的。
例如,如果 keyPrefix 的值是 "order",并且 date 的值是 "2025:04:09",那么最终的键名将是 "inc:order:2025:04:09"。
stringRedisTemplate.opsForValue().increment(...): 这是 Spring Data Redis 提供的操作,用于对指定的键执行递增操作。具体来说:- 功能: 在 Redis 中,将键
"inc:order:2025:04:09"对应的值加一。如果该键不存在,Redis 会先将其初始化为 0,然后再执行递增操作。 - 返回值: 返回递增后的值,即当前的计数值。
代码详解
/**
* 通过 Redis 实现了一个全局唯一 ID 生成器,适用于分布式系统中需要生成唯一标识符的场景。
*/
/**
* 1. 类的定义与依赖注入:
*
* @Component:将该类声明为 Spring 的组件,使其能够被 Spring 容器管理和自动扫描。
* @Autowired:自动注入 StringRedisTemplate,这是 Spring 提供的用于操作 Redis 的模板类。
*/
@Component
public class RedisIdWorker {
/**
* 2. 常量定义:
* BEGIN_TIMESTAMP:定义了一个起始时间戳,表示从该时间点开始计算。
* 这里的值 1640995200L 对应的是 2022 年 1 月 1 日 00:00:00 的 UNIX 时间戳(以秒为单位)。
* COUNT_BIT:表示序列号占用的位数,这里设置为 32 位。
*/
// 设置起始时间,我这里设定的是2022.01.01 00:00:00
public static final Long BEGIN_TIMESTAMP = 1640995200L;
// 序列号长度
public static final Long COUNT_BIT = 32L;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* nextId 方法:
*
* @param keyPrefix
* @return
*/
public long nextId(String keyPrefix) {
/* 1. 生成时间戳
LocalDateTime.now():获取当前的本地日期时间。
now.toEpochSecond(ZoneOffset.UTC):将当前时间转换为 UTC 时区的秒级时间戳。
timeStamp = currentSecond - BEGIN_TIMESTAMP:计算当前时间与起始时间的差值,得到从起始时间到现在的秒数。
*/
LocalDateTime now = LocalDateTime.now();
long currentSecond = now.toEpochSecond(ZoneOffset.UTC);
long timeStamp = currentSecond - BEGIN_TIMESTAMP;
/* 2. 生成序列号
now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")):
将当前日期格式化为字符串(格式为 "yyyy:MM:dd"),用于构造 Redis 的键。
stringRedisTemplate.opsForValue().increment("inc:" + keyPrefix + ":" + date):
在 Redis 中以 "inc:" + keyPrefix + ":" + date 为键,
对其值执行自增操作。该键的命名方式确保了不同业务(通过 keyPrefix 区分)在不同日期下的计数是独立的。
*/
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
long count = stringRedisTemplate.opsForValue().increment("inc:" + keyPrefix + ":" + date);
// 3. 拼接并返回,简单位运算
return timeStamp << COUNT_BIT | count;
}
}

浙公网安备 33010602011771号