【Redis】解决全局唯一 id 问题

news2024/11/16 0:01:44

永远要记得坚持的意义

一、全局唯一 id 场景

概念: 以订单表的 id 为例

使用自增 id 会产生的问题:

  • id 的规律性太明显,容易让用户猜测到一些信息
  • 受表单数据量的限制 —— 分布式存储时,会产生问题 (自增长,容易产生 id 重复)

因此,我们需要定义全局 id

二、全局 id 生成器

全局 id 生成器的特性:

分布式系统下用来生成全局唯一 id 的工具,其特性有:

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

全局唯一 id 生成的实现:

  • UUID
    16进制字符串,不单调递增,jdk自带的
  • Redis 自增
  • snowflake 算法
    对时钟依赖非常高
  • 数据库自增策略
    数据库单独设置一个自增表,获取自增,实现唯一效果 (单调自增的数据库版,性能没有 Redis 自增好)

我们本文主要介绍 使用 Redis 作为全局唯一 id 生成器

我们不直接使用 redis 自增的数值,而是拼接一些其他信息,主要拼接的信息如下:

符号位 + 时间戳 + 序列号

在这里插入图片描述

三、技术实现

封装好的 Redis 生成唯一 id 的工具类如下:

@Component
public class RedisIdWorker {

    private static final long BEGIN_TIME = 1670198400;

    private StringRedisTemplate stringRedisTemplate;

    private RedisIdWorker(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }


    public long nextId(String keyPrefix){
        // 1. 生成时间戳
        LocalDateTime localDateTime = LocalDateTime.now();
        long nowSecond = localDateTime.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIME;
        // 2. 生成序列号 —— 防止超过上限(Redis 自增的上限 2 的64 次幂) 解决方式:再拼接一个日期字符串
        // 2.1 获取当前日期
        String day = localDateTime.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + day);
        // 3. 拼接并返回
        return timestamp<<32 | count; // 前 32 位为时间戳 ,后 32 位为 序列号(即 count)
    }
    
    
//   生成时间戳开始时间 —— 我这里是 2022 / 12/ 5 日开始
//    public static void main(String[] args) {
//        LocalDateTime time = LocalDateTime.of(2022, 12, 5, 0, 0, 0);
//        long second = time.toEpochSecond(ZoneOffset.UTC);
//        System.out.println(second);
//    }
}

测试类代码:

@Slf4j
@SpringBootTest
class HmDianPingApplicationTests {
    @Autowired
    private RedisIdWorker redisIdWorker;

    // 500 线程的线程池子
    private ExecutorService es = Executors.newFixedThreadPool(500);

    @Test
    public void testIdWorker() throws InterruptedException {

        CountDownLatch latch = new CountDownLatch(200);
        // 每个线程生成 100 个 id
        Runnable task = () -> {
            for (int i=0; i<5; i++) {
                long id = redisIdWorker.nextId("order");
                log.info("id=" + id);
            }
            latch.countDown();
        };
        long begin = System.currentTimeMillis();
        for (int i=0; i<200; i++){
            es.submit(task);
        }
        latch.await();
        long end = System.currentTimeMillis();
        log.info("time=" + (end - begin));
    }
    
}

使用全局唯一 id 解决秒杀业务

    @Resource
    private RedisIdWorker redisIdWorker;

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Override
    @Transactional
    public Result seckillVoucher(Long voucherId) {

        // 1. 查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        // 2. 判断秒杀是否开始
        if(voucher.getBeginTime().isAfter(LocalDateTime.now())){
            return Result.fail("秒杀还未开始");
        }
        // 3. 判断秒杀是否已经结束
        if(voucher.getEndTime().isBefore(LocalDateTime.now())){
            return Result.fail("秒杀已经结束");
        }
        // 4. 判断库存是否充足
        if(voucher.getStock() < 1){
            return Result.fail("库存不足");
        }
        // 5. 扣除库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .update();
        if (!success){
            return Result.fail("库存不足");
        }
        // 6. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        // 订单的参数 —— 订单 id \ 代金券 id \ 用户 id
        // 生成唯一 id
        long id = redisIdWorker.nextId("order");
        voucherOrder.setId(id);
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.setUserId(1L);
        save(voucherOrder);
        return Result.ok();

    }

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

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

相关文章

讲理论,重实战!阿里内部SpringBoot王者晋级之路全彩小册开源

大家都知道&#xff0c;Spring Boot框架目前不仅是微服务框架的最佳选择之一&#xff0c;还是现在企业招聘人才肯定会考察的点&#xff1b;很多公司甚至已经将SpringBoot作为了必备技能。但&#xff0c;现在面试这么卷的情况下&#xff0c;很多人面试时还只是背背面试题&#x…

基于KDtree的电路故障检测算法的MATLAB仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 k-d树是每个节点都为k维点的二叉树。所有非叶子节点可以视作用一个超平面把空间分割成两个半空间。节点左边的子树代表在超平面左边的点&#xff0c;节点右边的子树代表在超平面右边的点。选择超…

企业数据安全如何落实?私有化知识文档管理系统效率部署

编者按&#xff1a;本文分析了数据安全性企业的重要性&#xff0c;特别是高保密企业单位&#xff0c;介绍了天翎知识文档管理群晖NA是如何保护企业数据安全的。 关键词&#xff1a;私有化部署&#xff0c;安全技术&#xff0c;数据备份&#xff0c;病毒防护&#xff0c;全网隔…

【zeriotier】win10安装zeriotier的辛酸泪

目录概述问题1&#xff1a;waiting for zeriotier system service问题2&#xff1a;Zerotier One 出现Node ID “unknown”问题3&#xff1a;一切正常&#xff0c;但是连不上服务器最终解决方法附录概述 背景&#xff1a;实验室的服务器是使用zeriotier组网的&#xff0c;因此…

字符串-模板编译

模板编译 编译就是一种格式转换成另一种格式的过程&#xff0c;这里主要讨论一下模板编译。模板字符串对比普通的字符串有很多的不同&#xff0c;模板字符串可以嵌套&#xff0c;并且模板字符串可以在内部使用${xxx}进行表达式运算以及函数调用&#xff0c;这些其实都是模板编…

DPDK Ring

无锁环ring是DPDK提供的一种较为基础的数据结构&#xff0c;其支持多生产者和多消费者同时访问。 经过我的经验&#xff0c;无锁结构的实现主要依靠两方面&#xff1a; 最终的数据交换一定要是原子级的操作&#xff0c;最常用到的自然就是比较后交换&#xff08;Compare And S…

Java项目:SSM个人博客网站管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目包含管理员与游客两种角色&#xff1b; 管理员角色包含以下功能&#xff1a; 发表文章,查看文章,类别管理,添加类别,个人信息管理,评论…

DeepSort目标跟踪算法

DeepSort目标跟踪算法是在Sort算法基础上改进的。 首先介绍一下Sort算法 Sort算法的核心便是卡尔曼滤波与匈牙利匹配算法 卡尔曼滤波是一种通过运动特征来预测目标运动轨迹的算法 其核心为五个公式&#xff0c;包含两个过程&#xff1a; 其分为先验估计&#xff08;预测&…

[附源码]计算机毕业设计人事管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

UE4中抛体物理模拟UProjectileMovementComponent

UE4中抛体物理模拟UProjectileMovementComponent1.简述2.使用方法3.绘制抛物曲线4.绘制抛物曲线1.简述 背景&#xff1a;实现抛体运动&#xff0c;反弹效果&#xff0c;抛物曲线等功能 通用实现可以使用spline绘制&#xff0c;物体按照下图接口可以根据时间更新位置 USplineC…

CN_MAC介质访问控制子层@CSMA协议

文章目录常用方法静态方法信道划分MAC特点动态方法随机访问MACCSMA协议CSMA/CD多点接入(或多点访问):载波监听Note:&#x1f388;碰撞检测碰撞:碰撞冲突过程传播时延对载波侦听的影响&#x1f388;争用期发现碰撞的最迟情况电磁波的速率是有限最短帧长&#x1f388;小结&#x…

CAD重复圆绘制机械图形

这次CAD必练图形第四个&#xff0c;这个图形主要用到了CAD圆、直线、修剪、旋转等多个命令&#xff0c;看着不简单&#xff0c;等绘制出来后就觉得还是挺简单的。 目标图形 操作步骤 1.使用CAD直线命令绘制一条水平的直线和四条垂直的直线&#xff0c;四条垂直的直线之间的距…

【网络层】DHCP协议(应用层)、ICMP、IPv6详解

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录DHCP------DHCP服务器来动态分配IP--------应用层协议----允许地址重用ICMP字段----差错报文、询问报文差错报文-----终点不可达无法交付--------源点抑制、拥塞丢数据&#xff08;现在废弃&#xff09;----…

JAVA小区物业管理系统(源代码+论文)

毕业设计(论文) [摘要] 物业管理系统是紧随当今时代发展的需要&#xff0c;目的在于实现不同的人员对物业系统的不同的需要&#xff0c;有利于社会的稳定和顺利发展。 关键词&#xff1a;小程序Applet&#xff1b;应用程序Application;数据库&#xff1b;数据库实现&#xf…

12.5 - 每日一题 - 408

每日一句&#xff1a;没有醒不来的早晨&#xff0c;弄不懂的题目&#xff0c;熬不过的迷茫&#xff0c;只有你不敢追的梦。 数据结构 1 在最后一趟排序开始之前&#xff0c;所有记录有可能都不在其最终位置上的是______。 A. 直接插入排序B. 冒泡排序C. 堆排序D. 快速排…

底层逻辑-理解Go语言的本质

1.Java VS Go语言 Java&#xff0c;从源代码到编译成可运行的代码 上图已经展示了这个过程:从Java的源代码编译成jar包或war包(字节码),最终运行在JVM中。 我们把Java源代码编译后的jar包或war包看成是工程师生产出来的产品&#xff0c;操作系统是一个平台&#xff0c;JVM就是…

【RCNN系列】RCNN论文总结

目标检测论文总结 【RCNN系列】 RCNN RCNN目标检测论文总结前言一、Pipeline二、模型设计1.warp2.SVM3.阈值设定4.box回归三、思考四、缺点前言 一些经典论文的总结。 一、Pipeline 首先传入Input image&#xff0c;利用Selective Search&#xff08;比较古老&#xff09;算法…

【计算机网络】数据链路层:拓展的以太网

在物理层拓展以太网&#xff1a; 使用光纤拓展&#xff1a;主机使用光纤和一对光纤调制解调器连接到集线器。 使用集线器拓展&#xff1a;使用集线器连成更大的以太网 集线器优点&#xff1a; 使原来不同碰撞域的计算机能够跨碰撞域通信&#xff0c;扩大了以太网覆盖的地理范…

GDB使用技巧和相关插件

GDB使用-小技巧 参考&#xff1a;《100个gdb小技巧》 链接中的文档有许多关于GDB的使用小技巧&#xff1b; $info functions - 列出函数的名称 $s/step - 步入&#xff0c;进入带有调试信息的函数 $n/next - 下一个要执行的程序代码 $call/print - 直接调用函数执行 $i/info …

jvm简介

.什么是JVM&#xff1f; JVM是Java Virtual Machine&#xff08;Java虚拟机&#xff09;的缩写&#xff0c;是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相…