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

目录

    • 一、前言
    • 二、如何通过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获取,自增序列一次性增加多个,然后将这个区间的值存储在本地缓存即可。

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

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

相关文章

创建云端服务器

1.申请云端服务器 每个账户有三个月的免费试用 我的服务器选择是centos7 &#xff0c;别选成win了。 2.创建实例 创建实例的步骤&#xff0c;阿里云有文档 介绍 大致就是 左边点实例 -》 顶部选你申请服务器时的地区-》下面就出现一条实例-》点更多 -》要重置实例密码 -》同一…

Docker安装ewomail

ewomail相关链接 官网官方安装文档gitee 开始安装 快速安装 wget -c https://down.ewomail.com/install-03.sh && sh install-03.sh 域名docker安装 创建docker容器 docker run -idt \-p 25:25 \-p 110:110 \-p 143:143 \-p 465:465 \-p 587:587 \-p 993:993 \-…

【带头学C++】----- 三、指针章 ---- 3.10 函数指针(补充基础知识)

1.函数指针 1.1 函数的返回值类型为指针类型 将函数内部的合法地址通过返回值 返回给函数外部使用 注意:函数不要返回普通局部变量的地址 分析&#xff1a; 在这段代码中&#xff0c;函数getAddr()返回一个指向局部变量data地址&#xff08;作用域是函数内部&#xff09;的指…

DevOps简介

DevOps简介 1、DevOps的起源2、什么是DevOps3、DevOps的发展现状4、DevOps与虚拟化、容器 1、DevOps的起源 上个世纪40年代&#xff0c;世界上第一台计算机诞生。计算机离不开程序&#xff08;Program&#xff09;驱动&#xff0c;而负责编写程序的人&#xff0c;被称为程序员&…

Django ModelSerializer 实现自定义验证详解

随着 Web 开发的日益复杂化&#xff0c;对数据验证的需求也日益增加。Django REST framework 提供了一套强大的、灵活的验证系统&#xff0c;帮助开发者轻松处理各种复杂情况。本文将重点探讨 Django ModelSerializer 中如何实现自定义验证。 1. 简介 Django ModelSerializer…

深度学习 opencv python 实现中国交通标志识别 计算机竞赛_1

文章目录 0 前言1 yolov5实现中国交通标志检测2.算法原理2.1 算法简介2.2网络架构2.3 关键代码 3 数据集处理3.1 VOC格式介绍3.2 将中国交通标志检测数据集CCTSDB数据转换成VOC数据格式3.3 手动标注数据集 4 模型训练5 实现效果5.1 视频效果 6 最后 0 前言 &#x1f525; 优质…

2023面试笔记四

1、gc导致的cpu冲高 排查是否为gc导致&#xff0c;看如下两点&#xff1a; gc频率和耗时 内存占用率 &#xff08;1&#xff09;gc频率和耗时有两种手段看&#xff1a; 第一种&#xff1a;根据gc日志的打印时间&#xff0c;可确定每次gc间隔的时间和耗时&#xff1a; 使用…

一个界面现代美观,色彩年轻化的Vue3+SpringBoot3前后端分离中后台管理脚手架

&#x1f4da; 在线文档 | ✨ 提交需求 | &#x1f680; 演示地址&#xff08;账号/密码&#xff1a;admin/admin123&#xff09; 简介 ContiNew Admin &#xff08;Continue New Admin&#xff09;中后台管理框架/脚手架&#xff0c;持续以最新流行技术栈构建&#xff0c;拥…

[git] cherry pick 将某个分支的某次提交应用到当前分支

功能&#xff1a;将某个分支的某次提交应用到当前分支 应用场景&#xff1a; 在合并分支时&#xff0c;是将源分支的所有内容都合并到目标分支上&#xff0c;有的时候我们可能只需要合并源分支的某次或某几次的提交&#xff0c;这个时候我们就需要使用到git的cherry-pick操作…

单链表(4)

看尾插函数 尾插函数跟头插函数唯一的不同就是找尾巴 尾插函数&#xff1a; 首先是动态申请一个新结点 把val放到新结点里面当新结点的data 然后在单链表里面找尾巴 比如说指针p找到尾巴了&#xff0c;现在将指针p指向新的结点&#xff0c;尾插就好了 这里的p类似于头插函…

Presto资源管理之Resource Groups And Selector

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

Java poi给docx中的关键字标记颜色

Java poi给docx中的关键字标记颜色 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId>&l…

用于图像处理的高斯滤波器 (LoG) 拉普拉斯

一、说明 欢迎来到拉普拉斯和高斯滤波器的拉普拉斯的故事。LoG是先进行高斯处理&#xff0c;继而进行拉普拉斯算子的图像处理算法。用拉普拉斯具有过零功能&#xff0c;实现边缘岭脊提取。 二、LoG算法简述 在这篇博客中&#xff0c;让我们看看拉普拉斯滤波器和高斯滤波器的拉普…

Java 并发编程面试题——重入锁 ReentrantLock

目录 1.ReentrantLock 是什么&#xff1f;2.✨什么是重入锁&#xff1f;ReentrantLock 是如何实现可重入特征的&#xff1f;3.公平锁和非公平锁有什么区别&#xff1f;ReentrantLock 分别是如何实现的&#xff1f;4.✨ReentrantLock 的实现原理是什么&#xff1f;5.为什么 Reen…

Sui与SUMM3R推出NFT增长计划,毕业即提供5万美元资助

为了寻找并支持专注于NFT的高质量初创企业&#xff0c;我们自豪地宣布在Sui上推出NFT增长计划SUMM3R。这是一个为种子期和初创期公司提供的综合计划&#xff0c;该加速器将利用Sui独特的NFT技术&#xff0c;为它们提供无与伦比的知识、连接和资源&#xff0c;以建立可持续的业务…

k8s 部署mqtt —— 筑梦之路

mqtt是干嘛的&#xff0c;网上有很多资料&#xff0c;这里就不再赘述。 --- apiVersion: apps/v1 kind: Deployment metadata:labels:app: mqttname: mqttnamespace: default spec:replicas: 1selector:matchLabels:app: mqttstrategy:rollingUpdate:maxSurge: 25%maxUnavaila…

Leetcode_2:两数相加

题目描述&#xff1a; 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff…

远程运维如何更高效的远程管理?向日葵的这几项功能会帮到你

具备一定规模的企业&#xff0c;其IT运维需求普遍会面临设备数量众多、难以统一高效管理、始终存在安全敞口等问题&#xff0c;尤其是针对分部广泛的无人值守设备时&#xff0c;更是如此。 举一个简单的例子&#xff0c;一台位于商圈的无人值守可互动广告机设备&#xff0c;所…

第18章Swing程序设计

Swing程序设计 Swing用于开发桌面窗体程序用于JDK的第二代GUI框架&#xff0c;其功能比JDK第一代GUI框架AWT更为强大&#xff0c;性能更加优良。但因为Swing技术推出时间太早&#xff0c;七性能&#xff0c;开发效率等不及一些其他的留下技术&#xff0c;所以目前市场大多数桌面…