目录
一、概念
全局唯一ID是指在分布式系统中,每个实体都有一个唯一的标识符,确保在不同的节点或服务之间能够唯一标识一个实体。这种唯一性对于数据的一致性和系统的可靠性至关重要。例如,在电商系统中,每个订单、用户和商品都需要一个全局唯一的ID来区分和管理。
全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:
二、全局唯一ID的生成方法
1. UUID:UUID是一种128位长的数字,它保证了在全球范围内的唯一性。UUID通过算法生成,理论上重复的概率极低。在Java中,可以通过
java.util.UUID
类来生成UUID。2. Redis自增:Redis作为一个高性能的键值存储系统,可以通过
INCR
命令来生成唯一的ID。这种方法适用于需要高并发生成ID的场景。3. 雪花算法(Snowflake):雪花算法由Twitter开发,能够在分布式系统中生成唯一的ID。它结合了时间戳、工作机器ID和序列号来保证ID的唯一性和有序性。
4. 数据库自增:一些数据库系统提供了自增字段功能,例如MySQL的
AUTO_INCREMENT,
这种方法简单易用,但在分布式系统中需要额外的同步机制来保证ID的唯一性。
应用场景和优缺点
UUID:优点是生成简单,无需中心化管理;缺点是长度较长,且生成性能较低,不适合大量生成ID的场景,且生成的ID中包含字符,不适合作为订单ID等场景。
Redis自增:优点是高性能,适合高并发场景;缺点是依赖于Redis服务器的稳定性和网络延迟。
雪花算法:优点是高性能和高并发,能够生成有序的ID;缺点是需要维护好时间同步和机器ID的分配。
数据库自增:优点是简单易用;缺点是在分布式系统中需要额外的同步机制,且单点压力较大。
三、Redis生成全局ID
3.1. 生成策略
我们为了增加ID的安全性,可以不直接使用Redis自增的数值,而是拼接一些其它信息:
ID的组成部分:
符号位:1bit,永远为0
时间戳:31bit,以秒为单位,可以使用69年
序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID
该生成策略是实际项目中运用比较普遍的方案,保证了足够的安全性,另外通过时间戳作为组合还有额外的好处:
1. 能够限制这个生成ID的上限,即一天下单的量的最大值为2^32,那么对于像淘宝这种双十一当天的秒杀场景,也能够完全满足。
2. 通过时间戳作为组合,我们想要查询某一天或者某个范围天数内的订单,也更为方便。
因此,我们通过对Redis自增的方式按上述方式进行优化后,再对Redis进行分布式集群部署来生成全局唯一ID,除了能满足要求的五大特性外,对于我们日常的诸如订单之类业务的查询需求,也能充分满足。
3.2. 代码
package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
@Component
public class RedisIdWorker {
/**
* 开始时间戳
*/
private static final long BEGIN_TIMESTAMP = 1640995200L;
/**
* 序列号的位数
*/
private static final int COUNT_BITS = 32;
private StringRedisTemplate stringRedisTemplate;
public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public long nextId(String keyPrefix) {
// 1.生成时间戳
LocalDateTime now = LocalDateTime.now();
long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
// 2.生成序列号
// 2.1.获取当前日期,精确到天
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
// 2.2.自增长
long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
// 3.拼接并返回
return timestamp << COUNT_BITS | count;
}
}
转换成2进制的效果如下: