前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis实战9-全局唯一ID

Redis实战9-全局唯一ID

原创
作者头像
凯哥Java
发布2023-01-29 09:23:49
1.3K0
发布2023-01-29 09:23:49
举报
文章被收录于专栏:凯哥Java

发布优惠券的时候,每个店铺都可以发布优惠券,当用户抢购的时候,优惠券表中的id如果使用数据库的自增长ID会存在以下问题:

1:id的规律太明显,容易被刷

2:当数据量很大的时候,会受到单表数据的限制

缺点场景分析:

id规律场景:如果我们的id具有太明显的规则,用户或者说商业对手很容易猜测出来我们的一些敏感信息,比如商城在一天时间内,卖出了多少单,这明显不合适。

单表限制:随着我们商城规模越来越大,mysql的单表的容量不宜超过500W,数据量过大之后,我们要进行拆库拆表,但拆分表了之后,他们从逻辑上讲他们是同一张表,所以他们的id是不能一样的, 于是乎我们需要保证id的唯一性。

全局ID生成器

全局ID生成器,是一种在分布式系统下用来生成全局唯一的ID工具,一般需要瞒住下列特性:

唯一性、高可用、递增性、安全性、高性能

全局唯一ID生成策略:

UUID、Redis自增长、雪花算法、数据库自增

Redis自增ID策略:

1:每天一个key,方便统计订单量;

2:ID都在是时间戳+计数器

实战:基于Redis拼接其他信息来实现全局唯一ID

全局唯一ID使用long类型的,其中时间戳是基于某一个时间点开始的。比如我们从2022.11.26 23:00:00开始,可以使用69年。

思考1:获取当前时间的秒:

思考2:时间戳,怎么计算的?

使用当前时间的秒-初始时间的秒

思考3:序列号怎么设置?

使用Redis的setnx命令,最好加上当前年月日

思考4:怎么拼接?

因为我们需要返回的是long类型的,如果使用string拼接后,还要转换。还要知道,使用string拼接,当并发量很大的时候,也会有性能问题。那么我们应该怎么处理的?

注意:我们再来看看全局唯一ID的格式。如上图,我们可以看出,共64位,其中符号位是1个,时间戳是31位。序列号是32位,发现什么了吗?如果我们把时间戳向左移动32位(因为序列号是32位。向左移动位置,空出给序列号使用),是不是就是符号位+时间戳的了?

凯哥推荐:Redis系列教程

本文由凯哥Java(kaigejava)发布

1:我们也知道计算机中左移动最快是x<<位数。

2:我们还需要知道,在计算机中 | 或计算:按位或运算“|”

根据上面,我们可以知道位运算序号后,就是序列号的值。序列号是多少,就是多少。

所以,我们可以知道拼接代码如下:timeStamp << 32 |no;

最终代码:

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;

/**

* @author 凯哥Java

* @description 基于Redis实现62位的全局唯一ID

* @company

* */

@Component

public class RedisIdWorker {

private static final long BEGIN_TIMESTAMP = 1669503600L;

private final StringRedisTemplate stringRedisTemplate;

/**

* 序列号的位数

*/

private static final int COUNT_BITS = 32;

public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {

this.stringRedisTemplate = stringRedisTemplate;

}

/**

* 获取id

* @param kyePrefix

* @return

*/

public long nextId(String kyePrefix){

//1:生成时间戳 = 当前时间戳-开始时间戳

LocalDateTime now = LocalDateTime.now();

long nowSecond = now.toEpochSecond(ZoneOffset.UTC);

long timeStamp = nowSecond - BEGIN_TIMESTAMP;

//2:生成序列号.使用sexNx.其中加上当前年月日

//获取当前时间的你那月日

String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));

//开始32位序列号

long no = stringRedisTemplate.opsForValue().increment("icr:"+kyePrefix+":"+date);

//3:拼接返回

return timeStamp << COUNT_BITS |no;

}

/**

* 获取到指定时间的毫秒

* @param args

*/

public static void main(String[] args) {

LocalDateTime time = LocalDateTime.of(2022,11,26,23,00,00);

long second = time.toEpochSecond(ZoneOffset.UTC);

System.out.println(second);

}

}

测试:使用多线程及countdownlatch

private ExecutorService executorService = Executors.newFixedThreadPool(500);

@Resource

private RedisIdWorker redisIdWorker;

@Test

public void RedisIdWorkerTest() throws InterruptedException {

CountDownLatch latch = new CountDownLatch(300);

Runnable task = ()->{

for(int i = 0;i<100;i++){

long id = redisIdWorker.nextId("myorder");

System.out.println(id);

}

latch.countDown();

};

long begin = System.currentTimeMillis();

for(int i = 0;i< 300;i++){

executorService.submit(task);

}

latch.await();

long endTime = System.currentTimeMillis();

System.out.println("耗时:"+(endTime-begin));

}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 全局ID生成器
  • 实战:基于Redis拼接其他信息来实现全局唯一ID
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档