1 全局唯一ID生成策略
每个店铺都可以发布优惠券:
当用户抢购时,就会生成订单并保存到tb_voucher_order这张表中,而订单表如果使用数据库自增ID就存在一些问题:
1. id的规律性太明显
2. 会受单表数据量的限制
全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足下列特性:
唯一、高可用、高性能、递增性、安全性
这就相当对应了我们Redis中String类型的方法,
第一:高可用、高性能,这是毋庸置疑的,毕竟是基于Redis的
第二:递增性
第三:安全性
对于安全性:我们可以不直接使用Redis自增的数值,而是拼接一些其它信息:
符号位:1位
时间戳:31位,可以使用69年
序列号:32位,使用INCRBY生成,秒内的计数器,支持每秒2^32个不同的id
实现方法如下:
1.获取时间戳
获取时间戳可以通过当前时间戳减去2022年1月1号的时间戳,获取时间戳的方法可以使用toEpochSecond方法转化为秒数。
2.获取序列号
获取序列号,因为为了不然redis中保存的key都为同一个,我们在key的后面加上了当前时间(今天),而且此做法还可以方便统计这一天生成的订单数
然后就是通过Redis中的INCREBY方法生成序列号。
3.拼接
拼接就比较讲究了,虽然可以直接使用加号等字符串拼接方法进行进行拼接,但是最后拼接出来的只能是字符串。
要想最后获得一个Long类型的ID,我们可以这样做:
首先分析一下这个id的组成:
第一位为符号位,然后就是31的时间戳,然后就是序号。
我们可以这么做,先把时间戳左移32位,这样后32位就只能是序列号。然后通过 |(或)来进行拼接序列号。(涉及位运算知识,计算机组成原理)
这里注意:使用 | 或的原因是最开始都是序列号上都是00000,不管你序列号来的是0还是1,
0 | (或)0还是0,0|(或)1也还是1,所以使用|(或)
实现代码如下:
package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
/**
* @Author 华子
* @Date 2022/11/28 19:54
* @Version 1.0
*/
@Component
public class RedisIdWorker {
@Resource
private StringRedisTemplate stringRedisTemplate;
//开始时间
private static final Long BEGIN_TIMESTAMP = 1640995200L;
private static final int COUNT_BITS = 32;
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("yyyyMMdd"));
//2.2生成序列号
long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
// 3.拼接并返回
return timestamp << COUNT_BITS | count;
}
}
这样就可以生成全局唯一ID啦~