在分布式系统中,我们经常需要生成全局唯一 ID:订单号、用户号、日志追踪 ID……如果还在用数据库自增 ID 或 UUID,要么性能不够,要么不易排序、浪费存储空间。
下面是一种Redis 自增 + 时间戳的高性能 ID 生成器实现方式,轻量、简单。
1. 为什么需要全局唯一 ID 生成器?
在单体应用时代,我们常用数据库的自增 ID。
但在分布式架构下,多个节点同时写数据库,或者有多个数据库分片时,就容易出现 ID 冲突或难以维护的主键策略。
常见方案:
- UUID:天然唯一,但过长(32位十六进制),不可排序,存储索引效率低。
- Snowflake 算法:经典的 Twitter 雪花算法,需要维护机器号、数据中心号,应用层实现较复杂。
- Redis 自增:天然分布式,性能高,可按业务/时间维度生成序列号。
今天这篇文章就是基于 Redis 自增的方案。
2. 设计思路
我们希望一个 ID 既能:
- 保证唯一:不同请求的 ID 不重复;
- 趋势递增:便于按时间排序;
- 高性能:在高并发下依旧稳定。
所以我们可以把 ID 分两部分:
高 32 位:时间戳(秒级),表示从某个固定时间点开始经过的秒数
低 32 位:序列号,表示当天第几个 ID
拼接公式:
return (时间戳 << 32) | 序列号
这样就得到一个 64 位 long 类型的 ID。
3. 完整代码实现
直接上代码:
/**
* 全局唯一ID生成器
*/
@Component
public class RedisIdWorker {
// 起始时间戳(2022-01-01 00:00:00 UTC)
private static final long BEGIN_TIMESTAMP = 1640995200L;
// 序列号占用位数
private static final int COUNT_BITS = 32;
private final StringRedisTemplate stringRedisTemplate;
public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 生成全局唯一ID
* @param keyPrefix 业务前缀(如 "order"、"user")
*/
public long nextId(String keyPrefix) {
// 1. 生成时间戳(秒)
long timeStamp = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) - BEGIN_TIMESTAMP;
// 2. 生成序列号:每天一个key,避免数字无限增长
String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
long count = stringRedisTemplate.opsForValue()
.increment("icr:" + keyPrefix + ":" + date);
// 3. 拼接时间戳和序列号
return timeStamp << COUNT_BITS | count;
}
}
4. 关键点解析
4.1 BEGIN_TIMESTAMP
这是一个基准时间戳(UTC 秒)。我们只存储从这个时间点开始的秒数,可以少占用位数。
比如这里设定的是 2022-01-01 00:00:00 UTC。
4.2 COUNT_BITS
序列号部分占用 32 位,表示一天最多可以生成 2^32 ≈ 42亿个 ID。
可以按需调整,如果业务量小,也可以调小。
4.3 “序列号回滚以天为单位”是什么意思?
这里每天生成的 Redis key 带有日期:
icr:order:2023:09:10
第二天自动用新的 key:
icr:order:2023:09:11
这样每天的序列号都从 1 开始计数(“回滚”),避免序列号无限增大。
4.4 使用 Redis 的好处
- Redis 单线程自增操作
INCR是原子性的; - 生成速度快(微秒级),支撑高并发;
- 分布式多节点共用一个 Redis,天然全局唯一。
5. 使用示例
在业务代码中直接注入:
@Autowired
private RedisIdWorker redisIdWorker;
public void createOrder() {
long orderId = redisIdWorker.nextId("order");
// 保存订单时使用 orderId
}
Redis 中自动生成类似这样的 key:
icr:order:2023:09:10 = 10001
最终返回的 long 型 ID 在数据库中可存为 bigint,既短又可排序。
6. 对比其它方案
| 方案 | 唯一性 | 可排序 | 存储效率 | 实现复杂度 |
|---|---|---|---|---|
| 数据库自增 | ✅ | ✅ | ✅ | 简单(但不适合分布式) |
| UUID | ✅ | ❌ | ❌ | 简单 |
| Snowflake | ✅ | ✅ | ✅ | 中等(需维护机器号) |
| Redis自增+时间戳 | ✅ | ✅ | ✅ | 简单、轻量 |
可以看到,这个方案非常适合中小型分布式系统。
7. 可能的优化
- 多 Redis 节点容灾:用 Redis 集群或者哨兵模式保证高可用。
- 批量预取:如果要减少 Redis 调用,可以批量拿一段序列号缓存在本地。
- 毫秒级时间戳:如果业务需要更高精度,可以改成毫秒。
8. 总结
本文基于 SpringBoot + Redis,结合时间戳 + 自增序列号,实现了一个高并发全局唯一 ID 生成器,既保证唯一性、趋势递增,又轻量好用。
你可以把这个类直接丢进项目里,几乎零改动就能用,非常适合订单号、流水号、日志追踪 ID 等场景。
