【Redis】Redis 生成唯一 id

每个订单业务都需要有一个唯一的id,如果使用数据库自增id就会暴露规律,同时id会有一个最大的阈值,万一订单超过这个阈值,那就会出现问题。因此我们可以封装一个全局ID生成器,可以适用于分布式系统生成唯一ID,一般需要满足唯一性、高可用性、递增性、安全性、高性能。有以下几种策略能够生成唯一ID。

1. UUID

这个直接利用JDK自带的工具类UUID工具类就能生成了。这种生成策略生成的其实是16进制的一长串的数值,因为这一长串是十六进制,因此它返回的结果其实是字符串结构,并且也不是单调递增的一种特性。因此虽然可以做唯一ID,但是并不够友好,没有满足之前我们所说的哪些特性,因此这种用的比较少。

2. Redis 自增

Redis满足上述的几个特性,而且是整体单调递增的,数值的长度不超过long类型,因为它是个数值类型,存储大小也不会太大。

3. snowflake 算法

雪花算法是世界上知名的全局唯一id生成策略,采用long类型的64位数字,原理和今天的Redis自增原理差别不大,只是雪花算法的自增是当前机器的自增,需要维护机器ID。同时,对时钟依赖度高,如果时间不准确,可能会出现异常。

4. 数据库自增

数据库自增不是将表id设置为自增,而是单独用一张表记录做自增。需要自增时就要从这张单独的表获取id进行自增。

这里将演示redis自增生成唯一id。为了增加ID的安全性,不直接使用Redis自增的数值,而是拼接一些其他信息。

如图所示,ID分为三部分。

符号位:最高位表示符号位,1bit,永远为0。

时间戳:31bit,以秒为单位,可以使用 69 年。

序列号:32bit,秒内计数器,支持每秒产生2^32个不同的ID。

实现

/**
 * Redis id 自增
 * */
@Component
public class RedisIdWork {

    /*开始时间戳,这个是使用main方法测试出来的。基于2020年1月1日至写下此代码的时间戳
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.of(2022, 1, 1, 0, 0, 0);
        long second = now.toEpochSecond(ZoneOffset.UTC);
        System.out.println(second);       // 1640995200
    }*/

    private static final long BEGIN_TIMESTAMP = 1640995200; //  基于2020年1月1日至写下此代码的时间戳
    private static final long COUNT_BITS = 32;    // 位数
    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWork(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    // 方法的返回值是Long,因为按照之前的策略,最终的id是一个64位的数字就是对应了Java的Long
    // 参数keyPrefix:生成策略是基于redis的自增长,redis的自增肯定是需要有一个key,然后值不断自增。
    // 不同的业务肯定有不同的key,大家不能都去用同一个自增长,因此这里需要有前缀去区分不同的业务,例如订单业务可以传order过来
    public long nextId(String keyPrefix) {
        // 1. 生成时间戳。此刻时间减去开始时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        // 2. 生成序列号
        // 2.1.获取当前日期,精确到天yyyyMMdd。这里用冒号分开,因为在redis中如果你的key用冒号分隔,这样在redis中就是分层级的
        // 当我要统计某天、某月、某年的订单量的时候,就可以很方便的利用前缀统计,例如统计一个月就可以使用yyyy:MM作为前缀
        String data = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2 自增长
         /*
         icr表示自增长,然后拼上业务(keyPrefix),但是到这key还不能结束。
         因为自增的值也会越来越大,而redis单个key的自增长对应的数值是有一个上限的,2的64次方。
         我们的key的策略里面,真正用来记录序列号的只有32个比特位,而redis是64比特位,超过64位很难,但是超过32位还是有可能的
         所以尽管是同一个业务,也不能使用同一个key,否则就很有可能会超过上限。
         办法:在业务前缀的后面,拼上一个时间戳,这个时间戳精确到天。这样的好处是将来如果想统计这一天一共下了多少单,那么直接看key的日期对应的值就行了,因此它还有一个统计效果
         注意:这里可能会报黄,说你这里拆箱可能会有空指针。但实际上这个方法并不会导致空指针,因为如果这个key不存在,它就会自动取给你创建一个key,并且从0开始,第一次自增长之后就是1了,所以你根本不用管这个警告
         */
        Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + data);
        // 3. 拼接id返回

        return timestamp << COUNT_BITS | count;
    }

}

测试

@SpringBootTest
class HmDianPingApplicationTests {

    @Resource
    private RedisIdWork redisIdWorker;

    // 线程池,创建500个线程
    private ExecutorService es = Executors.newFixedThreadPool(500);


    @Test
    void testIdWorker() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(300);

        // 线程任务
        Runnable task = () -> {
            for (int i = 0; i < 100; i++) {
                long id = redisIdWorker.nextId("order");
                System.out.println("id = " + id);
            }
        };
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 300; i++) {
            es.submit(task); // 提交300次,每个任务生成100个自增长id,因此一共30000个
        }
        long end = System.currentTimeMillis(); // 由于线程池是异步的,因此这里计时其实是没有意义的,因此这里会借助于CountDownLatch
        System.out.println("time = " + (end - begin));
    }

}

因为使用线程池是异步的,因此异步线程还没执行完毕,主线程已经执行完毕了,所以测量运行时间是没有意义的。这里要使用CountDownLatch,它有countDown 和 await 两个方法可以帮我们测量运行时间。

await 是阻塞方法,由于异异步线程没执行完,主线程就开始执行了,所以要阻塞主线程方法,等待异步线程执行完毕后再执行主线程方法。那主线程需要阻塞到什么时候呢?当CountDownLatch内部维护的变量为0时,开始让主线程方法执行。我们让异步线程和变量绑定,每调用一次countDown,内部变量就减1,当内部变量为0时,说明异步线程都执行完毕了,此时await就不在阻塞主线程,这样就能够计算出系统运行时间。

@SpringBootTest
class HmDianPingApplicationTests {

    @Resource
    private RedisIdWork redisIdWorker;

    // 线程池,创建500个线程
    private ExecutorService es = Executors.newFixedThreadPool(500);


    @Test
    void testIdWorker() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(300);

        // 线程任务
        Runnable task = () -> {
            for (int i = 0; i < 100; i++) {
                long id = redisIdWorker.nextId("order");
                System.out.println("id = " + id);
            }
            latch.countDown();
        };
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 300; i++) {
            es.submit(task); // 提交300次,每个任务生成100个自增长id,因此一共30000个
        }
        latch.await();
        long end = System.currentTimeMillis(); // 由于线程池是异步的,因此这里计时其实是没有意义的,因此这里会借助于CountDownLatch
        System.out.println("time = " + (end - begin));
    }

}

结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/937420.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

购物商城案例 -- VueCli创建项目,调整目录,vant组件库

基于VueCli创建项目 调整目录&#xff0c;新增两个目录 修改路由和App.vue 路由中规则清空 新建文件夹api和utils api文件夹&#xff1a;发请求的一些文件 utils文件夹&#xff1a;工具函数方法 vant组件库&#xff1a;第三方vue组件库 vant-ui 找到vant官网&#xff0c;进入va…

金融分析-Transformer模型(基础理论)

Transformer模型 1.基本原理 transformer的core是注意力机制&#xff0c;其本质就是编码器-解码器。他可以通过多个编码器进行编码&#xff0c;再把编码完的结果输出给解码器进行解码&#xff0c;然后得到最终的output。 1.1编码器 数据在编码器中会经过一个self-attention的…

【密码学】AES算法

一、AES算法介绍&#xff1a; AES&#xff08;Advanced Encryption Standard&#xff09;算法是一种广泛使用的对称密钥加密&#xff0c;由美国国家标准与技术研究院&#xff08;NIST&#xff09;于2001年发布。 AES是一种分组密码&#xff0c;支持128位、192位和256位三种不同…

朗致面试---IOS/安卓/Java/架构师

朗致面试---IOS/安卓/Java/架构师 一、面试概况二、总结三、算法题目参考答案 一、面试概况 一共三轮面试&#xff1a; 第一轮是逻辑行测&#xff0c;25道题目&#xff0c;类似于公务员考试题目&#xff0c;要求90分钟内完成。第二轮是技术面试&#xff0c;主要是做一些数据结…

51c嵌入式~单片机~合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/12362395 一、不同的电平信号的MCU怎么通信&#xff1f; 下面这个“电平转换”电路&#xff0c;理解后令人心情愉快。电路设计其实也可以很有趣。 先说一说这个电路的用途&#xff1a;当两个MCU在不同的工作电压下工作&a…

网络原理done

文章目录 ARP协议模拟一次ARP过程ARP周边问题ARP欺骗RARP DNS域名解析服务域名简介DNS结论 ICMP协议 NAT技术&#xff08;重点&#xff09;NAPTNAT缺点 内网穿透代理服务器正向代理反向代理 NAT和代理服务器区别 ARP协议 以这片区域为例 此时IP报文到达入口路由器R 此时路由器…

MATLAB中Simulink的信号线

Simulink以模块为最小单位,通过信号线互相连接&#xff0c;用户可通过GUI调配每个模块的参数,且仿真的结果能够以数值和图像等形象化方式具现出来。信号线可以传递一维数据、多维数据、向量数据或矩阵数据,甚至Bus型数据。Simulink使用不同的线形表示传递不同数据类型的信号线,…

集成方案 | Docusign + 泛微,实现全流程电子化签署!

本文将详细介绍 Docusign 与泛微的集成步骤及其效果&#xff0c;并通过实际应用场景来展示 Docusign 的强大集成能力&#xff0c;以证明 Docusign 集成功能的高效性和实用性。 在现代企业运营中&#xff0c;效率和合规性是至关重要的。泛微作为企业级办公自动化和流程管理的解决…

基于vue的quasarui框架和.NET CORE实现网站

首先安装quasar cli&#xff0c;然后进行配置 前台代码部分截图 后台部分截图 数据库 网站部分

一行代码解决vue3前端打包部署到服务器,动态配置http请求头后端ip方法教程无bug

只需要一行代码 vue3若依框架前端打包部署到服务器&#xff0c;需要部署到多个服务器上&#xff0c;每次打包会很麻烦&#xff0c;今天教大家一个简单的动态配置请求头api的方法&#xff0c;部署后能动态获取(修改)对应服务器的请求ip&#xff0c; 介绍两种方法&#xff0c;如…

openGauss开源数据库实战二十三

文章目录 任务二十三 openGauss 参数管理任务目标实施步骤一、启动参数文件及参数类型1.参数值修改后必须重新启动数据库的参数2.参数值修改后只需要reload操作的参数 二、设置数据库级参数三、设置用户级参数四、设置会话级参数五、将参数设置为默认值 任务二十三 openGauss 参…

杨振宁大学物理视频中黄色的字,c#写程序去掉(原版改进,三)

上一节&#xff0c;我们分清了主次矛盾&#xff0c;并搞定了主要矛盾&#xff08;去掉黄色的字&#xff09;&#xff0c;这一节解决次要矛盾&#xff08;矩形色带&#xff09;。 我们的想法如图&#xff1a; 1&#xff0c;我们找到稳定黄色的最左边&#xff0c;最右边两点&…

ORACLE逗号分隔的字符串字段,关联表查询

使用场景如下&#xff1a; oracle12 以前的写法&#xff1a; selectt.pro_ids,wm_concat(t1.name) pro_names from info t,product t1 where instr(,||t.pro_ids|| ,,,|| t1.id|| ,) > 0 group by pro_ids oracle12 以后的写法&#xff1a; selectt.pro_ids,listagg(DIS…

JS-手写new

我们先再来理一理原型 Object1 {name:deng,age:18 } Object2 {name:ru,age:18 } const Person function(){} Person.prototype Object1; const p1 new Person(); console.log(p1.name); //deng Person.prototype null; console.log(p1.name); //deng上面给Person的构造函…

LabVIEW实验站反馈控制系统

开发了一套基于LabVIEW的软X射线磁性圆二色实验站的反馈控制系统。这套系统主要用于实现对实验站高电压的精确控制&#xff0c;从而保持照射在样品上的流强稳定性&#xff0c;为分析样品吸收谱提供可靠基准&#xff0c;同时提供了易用的用户界面和强大的数据存储功能。 项目背景…

Matlab笔记---clear、clc、clear all应用

在MATLAB中&#xff0c;clear、clc 和 clear all 是三个常用的命令&#xff0c;它们各自有不同的作用&#xff1a; clc&#xff1a; clc 命令用于清除MATLAB命令窗口中的所有输出。它不会删除任何变量、函数或文件&#xff0c;只是清除屏幕上的显示内容&#xff0c;让你可以更…

Python Segmentation fault错误定位办法

1. 说明 Python3执行某一个程序时&#xff0c;报Segmentation fault (core dumped)错&#xff0c;但没有告知到底哪里出错&#xff0c;无法查问题&#xff0c;这时就需要一个库faulthandler来帮助分析。 2. 安装faulthandler faulthandler在Python3.3之后成为标准库&#xf…

康耐视智能相机(Insight)通过ModbusTCP发送字符串到倍福(BECKHOFF)PLC中

文章目录 1.背景2.分析3.实现3.1.PLC的ModbusTCP_Server3.1.1.安装TF6250-Modbus-TCP3.1.2.PLC设置 3.2.智能相机的ModbusTCP_Client3.2.1.了解ModbusTCP的协议3.2.2.根据协议写代码3.2.2.1.纯函数代码3.2.2.2.脚本代码 3.2.3.非脚本处理时的代码逻辑图3.2.4.关于代码的问题及解…

语音芯片赋能可穿戴设备:开启个性化音频新体验

在科技日新月异的今天&#xff0c;语音芯片与可穿戴设备的携手合作&#xff0c;正引领我们步入一个前所未有的个性化音频时代。这一创新融合&#xff0c;用户可以享受到更加个性化、沉浸式的音频体验。下面将详细介绍语音芯片与可穿戴设备合作的优点和具体应用。 1. 定制化音效…

医学图像之图像分割数据集视神经青光眼分割数据集labelme格式903张2类别

数据集格式&#xff1a;labelme格式(不包含mask文件&#xff0c;仅仅包含jpg图片和对应的json文件) 图片数量(jpg文件个数)&#xff1a;903 标注数量(json文件个数)&#xff1a;903 标注类别数&#xff1a;2 标注类别名称:["opticDisc","opticCup"] 每个类…