首页 > 数据库 >Redis实战9-全局唯一ID

Redis实战9-全局唯一ID

时间:2023-01-29 09:56:01浏览次数:40  
标签:实战 全局 Redis long id 序列号 ID

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

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

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

缺点场景分析:

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

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

全局ID生成器

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

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

71ed2335099c6f90fd97e5b7aa9b2e81.png

全局唯一ID生成策略:

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

Redis自增ID策略:

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

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

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

d1eda313100802c0e06709274b2a0632.png

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

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

3f7e603ec908f8c2febd76a5d53ea9ad.png

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

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

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

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

7c56056dbdba82c428ecb1c96ea7d280.png

思考4:怎么拼接?

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

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

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

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

f4f158cc14263b984ab3ccfe990d6e2b.png

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

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

本文由凯哥Java(公众号:kaigejava),个人博客:www.kaigejava.com 发布于博客园。

小福利

凯哥自己开发的,领取外卖、打车、咖啡、买菜、各大电商的优惠券的公¥众¥号。如下图:

527c8a82983e56a30f4299fc76e1956a.png

最终代码:

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));
    }

 

标签:实战,全局,Redis,long,id,序列号,ID
From: https://www.cnblogs.com/kaigejava/p/17071825.html

相关文章

  • Redis缓存的主要异常及解决方案
    作者:京东物流陈昌浩1导读Redis是当前最流行的NoSQL数据库。Redis主要用来做缓存使用,在提高数据查询效率、保护数据库等方面起到了关键性的作用,很大程度上提高系统的......
  • EF Corexxxxnstance with the same key value for {'Id'} is already being tracked.
    AsNoTracki或者全局禁用protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptionsBuilder){//关闭EFCore默认追踪opt......
  • 3.Id 生成器
    保存数据,标识数据唯一,我们需要用到Id生成器。 1.总结   总结一下Id生成器的几种方法  mongodbid算法  雪花算法   数据库Id  Redis  ......
  • PyTorch图像分类全流程实战--图像分类可解释性06
    教程同济子豪兄https://space.bilibili.com/1900783代码运行云GPU平台:https://featurize.cn/?s=d7ce99f842414bfcaea5662a97581bd1DFFhttps://jacobgil.github.......
  • 获取Wifi的InterfaceGuid
    internalclassProgram{staticvoidMain(string[]args){//ShowNetworkInterfaces();wifi.EnumerateAvailableNetwo......
  • 对标阿里P7面试Redis面试专题
    文章目录​​一、什么是Redis?简述它的优缺点。​​​​二、Redis与memcached相比有哪些优势?​​​​三、Redis支持哪几种数据类型?​​​​四、Redis主要消耗什么物理......
  • Redis实现分布式阻塞队列
    1.Redis分布式锁实现原理分布式锁本质上要实现的目标就是在Redis里面占一个“茅坑”,当别的进程也要来占时,发现已经有人蹲在那里了,就只好放弃或者稍后再试。占坑一般是......
  • Vue.js项目实战教程:智慧学成数据展示平台
    Vue作为前端框架三巨头之一,以其轻量易用著称,纵观它的整个发展也是极其迅速的。在前端的开发中,Vue已经成为每个前端开发者的必须掌握的技能。 下面播妞就来带大家用一篇文章......
  • 《RPC实战与核心原理》学习笔记Day11
    13|优雅关闭:如何避免服务停机带来的业务损失?我们在RPC架构下,需要考虑当服务重启时,如何做到让调用方系统不出问题。当服务提供方要上线时,一般是通过部署系统完成实例重......
  • IDEA社区版使用
    序章展示IDEA社区版的使用,并导入一个springboot项目运行。ben发布于博客园官网https://www.jetbrains.com/zh-cn/idea/ 下载&使用下载得到:ideaIC-2022.3.2......