为什么要生成全局唯一ID?
当我们做项目碰到一些业务比如生成订单时,要求订单id唯一,我们首先想到的可能就是使用数据库的主键生成自增的订单id,但是如果生成的订单过多,需要分表时,要确定多个表的订单id唯一,而数据库的主键自增只能确保一个表中订单id唯一并且自增,那么此时我们就需要通过其他方法来实现多个表的订单id唯一并且自增了。
基于Redis的字符串自增的思路
我们想要实现全局唯一ID并且实现自增,那么是不是需要定义一个变量,比如订单数量,每多一个订单该字段加一,然后将其保存到数据库中? 如果只是简单定义一个变量,会存在一些问题: 1、在分布式系统中,变量不被多个服务器共享。 解决方案:使用Redis的string类型存储 2、如果订单直接显示简单的序列号,那么订单号可能就会暴露一些信息,比如一天的订单 解决方案:可以拼接其他值,例如时间戳
思路实现
我们可以使用Long类型数据来作为订单id,Long类型是8个字节即64位,第一位代表符号,第二位到第三十二位可以存储时间戳,用来拼接保证订单id的安全性,第三十三位到六十四位可以存储序列号,序列号存储在redis的string类型中,一共可以生成2^32个订单号,为了防止订单过多存储不下并且可以统计每一天的订单,我们可以存储每一天的订单序列号在redis中。
代码实现
@Component
public class RedisWorker {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 表示2024年1月1日凌晨的时间戳
private static final long BEGIN_STAMPS = 1704067200;
private static final int BIT_COUNT = 32;
public long getGlobalId(String prefixKey) {
// 生成时间戳
LocalDateTime now = LocalDateTime.now();
long end = now.toEpochSecond(ZoneOffset.UTC);
// 记录时间间隔大小,使得三十一位能够存储更久
long timeStamps = end - BEGIN_STAMPS;
// 生成序列号
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
//记录每一天的订单数量
String key = "icr:" + prefixKey + ":" + date;
Long inc = stringRedisTemplate.opsForValue().increment(key);
//拼接时间戳和序列号
return timeStamps << BIT_COUNT | inc;
}
}