背景
分布式系统中由于跨进程跨系统,在某些场景中,我们需要生成全局的唯一ID,例如订单系统,并发情况下,不同的系统需要同时生成不一样的订单ID方便后续的订单下单与查询等等。
解决
网上有很多解决方法,例如:雪花算法,薄雾算法,利用单台数据库生成唯一主键方法,以及redis生成唯一ID方法,等等
redis生成全局唯一ID的原理
我们生成的订单号一般需要存在Long类型中,正好Long类型是64位,所以将第一位永远设置成0,表示正数。后面31位表示时间戳,可以表示的数字为2的31次方(0-2147483648),单位秒,再后面的32位可以表示成2的32次方的订单号(0-4294967296)。这种思想主要是借鉴雪花算法的原理。
解释
1.符号位:1bit,永远为0,表示正数
2.时间戳:31bit,最大2147483648秒,大概69年
3.序列号:32bit,最大4294967296,表示一秒中内能生成的不同的订单数(接近43亿)
一般一秒中能产生43亿个不一样的订单号,基本满足各种电商场景了。
java代码实现
@Component
public class RedisIdMaker {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 时间戳开始时间,从2022年1月1号0点0时0分开始
*/
private static final Long START_TIME = 1640995200L;
/**
* 订单生成数量 每天最多2的31次方个订单数量
*/
private static final int COUNT_BITS = 32;
private static final String ORDER_COUNT_KEY = "order:";
/**
* 根据redis生成唯一订单号
*
* @return
*/
public Long generateNextId() {
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
long currentStamp = now.toEpochSecond(ZoneOffset.UTC);
// 获取当前时间戳(秒)
long timeStamp = currentStamp - START_TIME;
// 组装成key=order:2022:01:01(组装成这种形式方便日后根据日期统计当天的订单数量)
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm"));
String redisKey = ORDER_COUNT_KEY + date;
// 订单自增长
long orderCount = stringRedisTemplate.opsForValue().increment(redisKey);
// 返回唯一订单号(拼接而来的)
return timeStamp << COUNT_BITS | orderCount;
}
/**
* 获取2022年1月1号0点0时0分的时间戳
* @param args
*/
public static void main(String[] args) {
LocalDateTime startLocalTime = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
long startTime = startLocalTime.toEpochSecond(ZoneOffset.UTC);
System.out.println(startTime);
LocalDateTime now = LocalDateTime.now();
String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm"));
System.out.println(date);
}
}
解释
时间戳开始是从1970年开始的,当31位时间戳全部是0的情况下,那么就是最开始,当当31位时间戳全部是1的情况下那么就是2039年,也就是说只能用到2039年
图中后32位表示2022年1月1号0点0时0分的秒时间戳。
timeStamp << COUNT_BITS
这行代码表示将上图中的时间戳往前位移32位,就变成了下面的
左移32位后.png
最后就是这段代码
timeStamp << COUNT_BITS |orderCount
| 是把某两个数中, 只要其中一个的某一位为1,则结果的该位就为1。
由于我们现在表示成二进制,只有0和1,所以这样运算后变成了
第一笔订单.png
测试
@Test
public void test() throws InterruptedException {
System.out.println(redisIdMaker.generateNextId());
}
结果
39001021761978369
进制转换
image.png
批量生成测试
@Test
public void contextLoads() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(300);
// 定义任务
Runnable task = ()->{
for (int i = 0; i < 100; i++) {
long id = redisIdMaker.generateNextId();
System.out.println(id);
}
countDownLatch.countDown();
};
for (int i = 0; i < 300; i++) {
es.submit(task);
}
countDownLatch.await();
}
结果:控制台一共生成了30000条ID
image.png
在看我们的redis,同样记录了,这一秒中生成的订单号数是30000
image.png总结
利用redis生成全局唯一ID,其实redis扮演的角色就是一个计数器的作用,方便后续的统计。
优点:高性能,高并发,唯一性,递增性,安全性。
缺点:需要依赖redis去实现
网友评论