使用 Redis 实现生成分布式全局唯一ID(使用SpringBoot环境实现)

news2024/11/28 18:44:39

目录

    • 一、前言
    • 二、如何通过Redis设计一个分布式全局唯一ID生成工具
      • 2.1、使用 Redis 计数器实现
      • 2.2、使用 Redis Hash结构实现
    • 三、通过代码实现分布式全局唯一ID工具
      • 3.1、编写获取工具
      • 3.2、测试获取工具
    • 四、总结

一、前言

       在很多项目中生成类似订单编号、用户编号等有唯一性数据时还用的UUID工具,或者自己根据时间戳+随机字符串等组合来生成,在并发小的时候很少出问题,当并发上来时就很可能出现重复编号的问题了,单体项目和分布式项目都是如此,要想解决这个问题也有很多种方法,可以自己写一个唯一ID生成规则,也可以通过数据库来实现全局ID生成这个和使用Redis实现其实类似,还可以使用比较成熟的雪花算法工具实现,每种方法都有各自的优缺点这里不展开说明,这里详细说明如何使用Redis实现生成分布式全局唯一ID。
       还有一个问题为什么不能直接使用数据库的自增ID,而是需要单独生成一个分布式全局唯一ID,类似订单IDON202311090001,在数据库中有自增ID,对于当前业务来说就是唯一的为什么不能用,还要去生成一个独立的订单ID,对于这个问题要从几个方面分析:
       1、数据库自增ID是有序增长的很容易就被人猜到,比如我现在下一单看到的订单ID为999那么就知道你的系统里最多只有999单,还有如果接口设计不合理,比如取消订单接口只校验了用户是否登录没有校验订单是否属于该用户,接收一个订单ID就能将订单取消,那么这样很容易就被人抓住漏洞,类似的情况有很多,也很多人写接口是不会注意这个问题。
       2、这种自增ID没有意义,而且不同业务的自增ID是重合的,对于信息区分度很低,而且考虑到多业务交互和用户端展示也都是不合适的,想想看要是你在某宝下单,订单ID是999,或者在对接别人订单系统时,给你的订单ID是999是不是很奇怪。
       3、分库分表时自增ID会重复

需要集成文章可以查看:
SpringBoot集成Lettuce客户端操作Redis:https://blog.csdn.net/weixin_44606481/article/details/133907103

二、如何通过Redis设计一个分布式全局唯一ID生成工具

       用户下单调用下单逻辑,先进行业务逻辑处理,然后携带订单ID标识通过分布式全局唯一ID工具获取一个唯一的订单ID,这个订单ID标识就是用于区分业务的,获取到订单ID后将数据组装入库,分布式全局唯一ID工具可以做成一个内嵌的utils,也可以封装成一个独立的jar,还可以做成一个分布式全局唯一ID生成服务供其它业务服务调用。

在这里插入图片描述

2.1、使用 Redis 计数器实现

       Redis的String结构提供了计数器自增功能,类似Java中的原子类,还要优于Java的原子类,因为Redis是单线程执行的缓存读写本身就是线程安全的,也不用进行原子类的乐观锁操作,每一次获取分布式全局唯一ID时就将自增序列加1。

# 给key为GENERATEID:NO的value自增1,如果这key不存在则会添加到Redis中并且设置value为1
## GENERATEID:key前缀
## NO:订单ID标识
127.0.0.1:6379> incr GENERATEID:NO
(integer) 1

2.2、使用 Redis Hash结构实现

       Redis Hash结构中的每一个field也可以进行自增操作,可以用一个Hash结构存储所有的标识信息和自增序列,方便管理,比较适合并发不高的小项目所有服务都是用的一个Redis,如果并发较高就不合适了,毕竟Redis操作普通String结构肯定比操作Hash结构快。

# 给key为GENERATEID,field为no的value自增1,如果这key不存在则会添加到Redis中并且设置value为1
## GENERATEID:分布式全局唯一ID Hash key
## NO:Hash结构中的field
127.0.0.1:6379> hincrby GENERATEID NO 1
(integer) 1

三、通过代码实现分布式全局唯一ID工具

       这里使用Redis 计数器实现,自增序列以天为单位存储,在实际业务中,比如生成订单编号组成规则都类似NO1699631999000-1(业务标识key+当前时间戳+自增序列),这个规则可以自己定义,保证最终生成的订单编号不重复即可,不建议直接一个自增序列干到底,订单编号这类型的数据都是有长度限制的,或者是要求生成20字符的订单编号,如果增长的过长反而不好处理。

3.1、编写获取工具

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.util.concurrent.TimeUnit;

@Component
public class RedisGenerateIDUtils {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    // key前缀
    private String PREFIX = "GENERATEID:";

    /**
     * 获取全局唯一ID
     * @param key 业务标识key
     */
    public String generateId(String key) {
        // 获取对应业务自增序列
        Long incr = getIncr(key);
        // 组装最后的结果,这里可以根据需要自己定义,这里是按照业务标识key+当前时间戳+自增序列进行组装
        String resultID = key + System.currentTimeMillis() + "-" + incr;
        return resultID;
    }

    /**
     * 获取对应业务自增序列
     */
    private Long getIncr(String key) {
        String cacheKey = getCacheKey(key);
        Long increment = 0L;
        // 判断Redis中是否存在这个自增序列,如果不存在添加一个序列并且设置一个过期时间
        if (!redisTemplate.hasKey(cacheKey)) {
            // 这里存在线程安全问题,需要加分布式锁,这里做简单实现
            String lockKey = cacheKey + "_LOCK";
            // 设置分布式锁
            boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 30, TimeUnit.SECONDS);
            if (!lock) {
                // 如果没有拿到锁进行自旋
                return getIncr(key);
            }
            increment = redisTemplate.opsForValue().increment(cacheKey);
            // 我这里设置24小时,可以根据实际情况设置当前时间到当天结束时间的插值
            redisTemplate.expire(cacheKey, 24, TimeUnit.HOURS);

            // 释放锁
            redisTemplate.delete(lockKey);
        } else {
            increment = redisTemplate.opsForValue().increment(cacheKey);
        }

        return increment;
    }

    /**
     * 组装缓存key
     */
    private String getCacheKey(String key) {
        return PREFIX + key + ":" + getYYYYMMDD();
    }

    /**
     * 获取当前YYYYMMDD格式年月日
     */
    private String getYYYYMMDD() {
        LocalDate currentDate = LocalDate.now();
        int year = currentDate.getYear();
        int month = currentDate.getMonthValue();
        int day = currentDate.getDayOfMonth();
        return "" + year + month + day;
    }
}

3.2、测试获取工具

import com.redisscene.utils.RedisGenerateIDUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.*;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedisSceneApplication.class)
public class RedisGenerateIDTest {
    @Autowired
    private RedisGenerateIDUtils redisGenerateIDUtils;

    @Test
    public void t1() throws InterruptedException {
        // 定义一个线程池 设置核心线程数和最大线程数都为100,队列根据需要设置
        ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 100, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10000));
        CountDownLatch countDownLatch = new CountDownLatch(10000);

        long beginTime = System.currentTimeMillis();
        // 获取10000个全局唯一ID 看看是否有重复
        CopyOnWriteArraySet<String> ids = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 10000; i++) {
            executor.execute(() -> {
                // 获取全局唯一ID
                long beginTime02 = System.currentTimeMillis();
                String orderNo = redisGenerateIDUtils.generateId("NO");
                System.out.println("获取单个ID耗时 time=" + (System.currentTimeMillis() - beginTime02));
                if (ids.contains(orderNo)) {
                    System.out.println("重复ID=" + orderNo);
                } else {
                    ids.add(orderNo);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        // 打印获取到的全局唯一ID集合数量
        System.out.println("获取到全局唯一ID count=" + ids.size());
        System.out.println("耗时毫秒 time=" + (System.currentTimeMillis() - beginTime));
    }
}

在这里插入图片描述

四、总结

       通过测试可以看到100并发生成全局唯一ID是没问题的,而且获取单个ID耗时在10-20毫秒左右,一般的业务已经完全够用,这个耗时也要看Redis性能和项目配置决定的,如果对于这种唯一ID生成并发量非常高的业务,可以提前生成一个唯一ID池存储在本地内存中,业务要获取唯一ID先去池中获取,如果获取不到再去Redis获取,自增序列一次性增加多个,然后将这个区间的值存储在本地缓存即可。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

山西电力市场日前价格预测【2023-11-11】

日前价格预测 ​ 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-11-11&#xff09;山西电力市场全天平均日前电价为311.30元/MWh。其中&#xff0c;最高日前电价为417.73元/MWh&#xff0c;预计出现在08: 00。最低日前电价为151.48元/MWh&#xff0c…

ESP32建立TCP连接

ESP32建立TCP连接 1.搭建ESP-IDF开发环境 搭建开发环境直接从官网下载即可。 https://docs.espressif.com/projects/esp-idf/zh_CN/v5.1.1/esp32s3/index.html https://dl.espressif.com/dl/esp-idf/?idf4.4 使用官方的下载器下载好&#xff0c;就可以自动安装&#xff0…

solidworks对电脑要求高吗?2023solidworks配置要求

solidworks对电脑要求高吗&#xff1f;SolidWorks是一款功能强大的三维CAD软件&#xff0c;对电脑配置有一定的要求。一般来说&#xff0c;运行SolidWorks需要的电脑配置包括较高的处理器性能、足够的内存和存储空间&#xff0c;以及一块性能良好的显卡。此外&#xff0c;对于大…

[Machine Learning] 多任务学习

文章目录 基于参数的MTL模型 (Parameter-based MTL Models)基于特征的MTL模型 (Feature-based MTL Models)基于特征的MTL模型 I&#xff1a;基于特征的MTL模型 II&#xff1a; 基于特征和参数的MTL模型 (Feature- and Parameter-based MTL Models) 多任务学习 (Multi-task Lear…

基于SSM的学院就业信息网设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

基于SSM的自习室预订座位管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

腾讯待办将停止运营并下架,如何把ics文件导入到日程APP?

相信有不少腾讯待办小程序的用户都发现了前段时间弹出来的“业务关停通知”弹窗&#xff0c;根据通知内容可知&#xff0c;由于业务调整&#xff0c;腾讯待办将于2023年的12月20日全面停止运营并下架&#xff0c;之后我们都不能够继续使用这款待办小程序了。 那么我们在腾讯待…

银行余额修改生成器,虚拟农业建设工商邮政中国,画板+取快照生成png高清图

在网上找了很多模版&#xff0c;一共好几个&#xff0c;然后都插入到了图片资源库里面&#xff0c;点击指定的单选框就会自动更换易语言画板上面的图片&#xff0c;然后模版上面都对应了指定的标签【透明状态覆盖了原有的字符】&#xff0c;然后在指定的参数上面对应加入了指定…

【C语言 | 预处理】C语言预处理详解(二) —— #pragma指令介绍以及内存对齐、结构体大小

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

送水服务预约小程序内容该如何做

无论小区还是办公楼等场景&#xff0c;送水服务往往有较高需求&#xff0c;同时该服务属于长期稳定性的&#xff0c;因此对品牌来说&#xff0c;如何打造品牌获取更多用户及转化非常重要&#xff0c;然而在实际订水过程中&#xff0c;又会面临着一些难题&#xff1a; 1、品牌传…

Facebook广告账户限制原因?一文带你避雷

很多小伙伴在facebook开启企业号进行投放广告&#xff0c;经常会出现广告投放被限制&#xff0c;不能创建广告或者更新一些设置被限制不仅业务受到阻碍&#xff0c;影响进度&#xff0c;造成损失&#xff0c;更有可能会失去一个广告账户。 那么被限制的原因会有哪些&#xff0…

PSP - 蛋白质复合物结构预测 模版配对(Template Pair) 逻辑的特征分析

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/134328447 在 蛋白质复合物结构预测 的过程中&#xff0c;模版 (Template) 起到重要作用&#xff0c;提供预测结果的关于三维结构的先验信息&…

从硬件“卷”到UI交互,车企怎样才能掌握智能化「灵魂」

随着汽车智能化为座舱交互带来的超越传统汽车的感知能力和算力&#xff0c;车企在视觉体验设计&#xff08;包括仪表、车机、HUD的UI设计以及HMI相关业务模块&#xff0c;比如智驾视觉交互&#xff09;的布局&#xff0c;正在进入新周期。 与此同时&#xff0c;交互逻辑和UI设计…

基于SSM的食用菌菌棒溯源系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

APP安全测试详解

在工作过程中&#xff0c;我接触到了一些SDL安全提测的工作。原来我是学web端渗透比较多的&#xff0c;移动端这块基本没怎么试过手&#xff0c;结果刚开始一直踩坑&#xff0c;连抓包都抓不到(&#xff34;▽&#xff34;)。 下面记录下我遇到的部分问题和解决方法&#xff0…

史上最细,Jenkins插件Allure生成自动化测试报告详细...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Allure介绍 A…

媒体转码软件Media Encoder 2024 mac中文版功能介绍

Media Encoder 2024 mac是一款媒体转码软件&#xff0c;它可以将视频从一种格式转码为另一种格式&#xff0c;支持H.265、HDR10等多种编码格式&#xff0c;同时优化了视频质量&#xff0c;提高了编码速度。此外&#xff0c;Media Encoder 2024还支持收录、创建代理和输出各种格…

Presto资源管理之Resource Groups And Selector

文章目录 前言资源组配置选择器规则 Selector Rules全局配置 Global Properties选择器属性配置案例配置 prestoDb 前言 资源组对资源使用进行限制&#xff0c;并可以对在其中运行的查询执行队列策略&#xff0c;或将资源分配给子组。查询属于单个资源组&#xff0c;并且从该组…

uniapp+vue3+ts+vite+echarts开发图表类小程序,将echarts导入项目使用的详细步骤,耗时一天终于弄好了

想在uniapp和vue3环境中使用echarts是一件相当前卫的事情&#xff0c;官方适配的还不是很好&#xff0c;echarts的使用插件写的是有些不太清晰的&#xff0c;这里我花费了一天的时间&#xff0c;终于将这个使用步骤搞清楚了&#xff0c;并且建了一个仓库&#xff0c;大家可以直…

工业自动化与物联网技术的融合:开启智能制造新时代

工业自动化与物联网技术的融合&#xff1a;开启智能制造新时代 随着科技的飞速发展&#xff0c;工业自动化与物联网技术的融合已经成为现代制造业的重要发展趋势。本文将分析工业自动化与物联网技术的关系、应用场景以及面临的挑战&#xff0c;并展望未来的发展趋势。 一、工业…