redis(7)

news2024/9/23 1:34:34

全局ID生成器: 

全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具,一般要满足以下特性

  1. 唯一性
  2. 高可用(随时访问随时生成)
  3. 递增性
  4. 安全性(不能具有规律性)
  5. 高性能(生成ID的速度快)

为了增加ID的安全性,我们不会使用redis自增的数值,而是使用拼接一些其他的信息

就算时间戳相同,也是可以在每一秒内支持2^32个订单

唯一ID组成部分:

符号位:1bit,永远为0,表示此ID永远是正数

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

序列号:32bit,秒内的计数器,可以支持2^32个不同ID

  • Redis自增ID策略
    • 每天一个key,方便统计订单量
    • ID构造是时间戳+计数器
@Component
public class RedisIDWorker {
    @Autowired
    private StringRedisTemplate template;
    public long NextID(String keyPrefix){
        //1.生成时间戳
        long current=System.currentTimeMillis();
        //2.生成序列号
        //2.1获取到日期的时间,让这一天的订单自增,还可以方便做统计
        SimpleDateFormat format=new SimpleDateFormat("yyyy:MM:dd");
        String data=format.format(System.currentTimeMillis());
       long count= template.opsForValue().increment("increment:"+keyPrefix+":"+data);
        System.out.println(count+"jjjj");
        //3.拼接并且返回
        return current<<32|count;
    }

}

 

 

@SpringBootTest
class RedisIDWorkerTest {
   @Autowired
   private RedisIDWorker worker;
    @Test
    void nextID() throws InterruptedException {
        CountDownLatch latch=new CountDownLatch(300);
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
               Long ID= worker.NextID("order业务");
                System.out.println(ID);
                latch.countDown();
            }
        };
        ExecutorService service= Executors.newFixedThreadPool(1);
        for(int i=0;i<300;i++){
            service.submit(runnable);
        }
        latch.await();
        System.out.println("线程池中的任务已经全部完成");
    }
}

 

实现获取优惠卷:

1)秒杀是否开始或结束,如果尚未开始或已经结束则无法下单

2)库存是否充足,不足则无法下单

加上事务的原因是因为想要进行修改优惠卷的剩余个数的数据库操作和新增订单的操作要么全部执行成功,要么全部执行失败;

@Controller
public class UserController {
    @Autowired
    private DemoMapper mapper;
    @Autowired
    private RedisIDWorker worker;
    @RequestMapping("/GetCard")
    @ResponseBody
    @Transactional
    public String GetOrder(Integer cardID,Integer userID){
          //在这里面还应该加上用户ID对应的用户是否存在
        //1.进行判断优惠卷ID是否存在
        Card card=mapper.SelectCardByID(cardID);
        if(cardID==null){
            return "当前优惠卷不存在";
        }
        //2.判断秒杀是否开始
        if(new Timestamp(System.currentTimeMillis()).compareTo(card.getStartTime())<0){
            return "秒杀活动还没有开始";
        }
        //2.判断秒杀活动是否结束
//        if(new Timestamp(System.currentTimeMillis()-500000).compareTo(card.getStartTime())>0){
//            return "秒杀活动已经结束";
//        }
        //3.判断代金卷是否还充足
        if(card.getCards()<1){
            return "当前优惠卷已经没有库存了";
        }
        //4.进行扣减库存
        int data= mapper.DecrmentCardData(cardID);
        //5.进行新创建订单
        if(data<1){
            return "获取优惠卷失败,优惠卷已经没有库存了";
        }
        Order order=new Order();
        order.setOrderID((int) worker.NextID("优惠卷秒杀"));
       //在实际开发中其实可以在session中获取到userID,当前为了实现方便只是在方法中传递了userID
        order.setUserID(userID);
        order.setCardID(cardID);
        mapper.InsertOrder(order);
        return "获取优惠卷成功";
    }
}

关于超卖(优惠卷被减成负数),超卖问题就是典型的多线程并发安全问题,针对这一问题的常见解决方案就是加锁:

假设线程1再进行查询库存和删除库存的过程中,还没有删除库存,那么有其他线程在中间插入一些逻辑就会造成线程安全问题 

1)线程1进行查询库存发现库存中只剩下一个优惠卷了

2)此时在线程1进行查询之后更新库存中的数据之前(cards=cards-1)

3)此时线程2进行尝试获取优惠卷,判断当前库存中只是剩下一个优惠卷了,此时线程2的时间片用完了,进入到休眠操作

4)此时线程1开始进行更新库存数据,此时优惠卷已经为0了

5)但是此时线程2被唤醒,因为唤醒之前已经判断过优惠卷还有1个,感知不到线程1进行更新了库存,此时线程2更新数据库,于是优惠卷就被减为了1

update card set cards=cards-1 where cardID=#{cardID}

 1)悲观锁:

        认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行

        例如Synchronized、Lock都属于悲观锁
 2)乐观锁:

        认为线程安全不一定会发生,因此不加锁,只是在更新数据时取判断有没有其他线程对数据做了修改

        如果没有修改则认为时安全的,自己才更新数据,一般用于更新数据
        如果已经被其他线程修改说明发生了安全问题,此时可以重试或异常
关于超卖我们可以加一个乐观锁,乐观锁的关键是判断之前查询得到的数据有被修改过,常见的方式有两种:

1)版本号法:顾名思义,就是在修改数据时加入一个version,如果修改的时候version与自己得到的version 不相同,那么就修改失败,可以尝试重试或异常
2)CAS法:就是更改数据,或者删除库存的时候,判断库存是否大于0,如果大于零,则扣除成功

 

但是如果这样当我们进行模拟100个用户进行并发访问的时候,就会发现:很少的用户能够抢到优惠卷,这是不符合业务逻辑的,但是此时还发现优惠卷的剩余次数还是大于0的,但是用户还抢不到优惠卷,这又是怎么回事呢?

1)假设此时线程1查询库存发现现在库存还有100个优惠卷

2)此时线程2也进行查询库存发现库存还有100个优惠卷

3)此时线程2的时间片用完,进入到阻塞状态

4)此时线程1的用户执行完获取到优惠卷的操作,库存中的优惠卷总数-1

5)此时线程2的用户开始被唤醒,执行获取到优惠卷操作,此时SQL语句执行失败

update card set cards=cards-1 where cardID=#{cardID} and cards=#{count}

因为此时总的库存数已经不和刚才线程2查询出来的库存数相等了,所以线程2执行数据库操作失败,所以线程2的用户获取到优惠卷失败,解决方案还是进行修改SQL语句;

update card set cards=cards-1 where cardID=#{cardID}and cards>0

一人一单的功能:在进行更新优惠卷数目的时候根据优惠卷ID和用户ID来进行查询订单,如果查询的订单不为空,那么直接返回false

但是此时做多线程并发访问的时候(用户传入了userID和CardID来去访问请求,结果又发现相同的用户ID下了很多单,于是又发生了线程安全问题;


@Controller
public class UserController {
    @Autowired
    private DemoMapper mapper;
    @Autowired
    private RedisIDWorker worker;
    @RequestMapping("/GetCard")
    @ResponseBody
    @Transactional
    public String GetOrder(Integer cardID,Integer userID){
          //在这里面还应该加上用户ID对应的用户是否存在
        //1.进行判断优惠卷ID是否存在
        Card card=mapper.SelectCardByID(cardID);
        if(cardID==null){
            return "当前优惠卷不存在";
        }
        //2.判断秒杀是否开始
        if(new Timestamp(System.currentTimeMillis()).compareTo(card.getStartTime())<0){
            return "秒杀活动还没有开始";
        }
        //2.判断秒杀活动是否结束
//        if(new Timestamp(System.currentTimeMillis()-500000).compareTo(card.getStartTime())>0){
//            return "秒杀活动已经结束";
//        }
        //3.判断代金卷是否还充足
        Order orderDemo=mapper.SelectOrder(userID,cardID);
        if(orderDemo!=null){
            return "您当前已经下过单了,无法再次进行购买";
        }
        int count=card.getCards();
        if(count<1){
            return "当前优惠卷已经没有库存了";
        }
        //4.进行扣减库存
        int data= mapper.DecrmentCardData(cardID);
        //update card set cards=cards-1 where cardID=#{cardID} and cards=#{count}
        //在这里面说明如果如果cards的值和上面第三部查询出优惠卷的count值是相等的,那么就直接进行更新操作,否则就执行失败
        //5.进行新创建订单
        if(data<1){
            return "获取优惠卷失败,优惠卷已经没有库存了";
        }
        Order order=new Order();
        order.setOrderID((int) worker.NextID("优惠卷秒杀"));
        order.setUserID(userID);
        order.setCardID(cardID);
        mapper.InsertOrder(order);
        return "获取优惠卷成功";
    }
}

这个线程安全问题就很好理解了,假设有100个线程来同时进行访问我们的代码,这100个线程同时并行执行(100个线程同时执行代码的每一句),那么就又会同时插入多个订单了

所以我们要给查询出订单到判断订单到插入订单的这些逻辑进行从加上悲观锁

 此时我们把新增订单和

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

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

相关文章

yolov4

1 V4版本概述 集各种优秀方案于一身&#xff0c;嫁接了众多主流的目标识别方面的情况。 V4 贡献 3. 数据增强策略分析 BOF Bag of freebies(BOF) Mosiac 数据增强 Mixup 比如将狗和猫的两张图片混合&#xff0c;一半猫&#xff0c;一半狗。 label 也变成 Dog 0.5 , Cat 0…

JAVA中PO、VO、BO、POJO、DAO、DTO、TO的理解

目录 1.阿里规范 1.1.Service/DAO 层方法命名规约 1.2.领域模型命名规约 1.3.命名风格 2.简单类&#xff1a;包括 DO/DTO/BO/VO 等 3.与MVC三层架构的关系 4.总结 4.1.为什么要分这些对象 4.2.什么时候需要定义这么多O 4.3.实体对象之间如何转换&#xff1f; 参考资…

Ground-aware Monocular 3D Object Detection for Autonomous Driving论文

1 摘要 摘要&#xff1a;使用单个RGB相机估计环境中物体的3D位置和方向是低成本城市自主驾驶和移动机器人的一项至关重要的挑战性任务。大多数现有算法基于二维-三维对应中的几何约束&#xff0c;这源于一般的6D目标姿态估计。我们首先确定地平面如何在驾驶场景的深度推理中提…

RT-Thread 5.0.1 qemu-virt64-aarch64 解决编译问题

前言 最近在最新的 RT-Thread 上搭建 bsp qemu-virt64-aarch64 的编译环境&#xff0c;发现较新的 gcc 交叉编译器编译失败了。 经过尝试较旧版本的 gcc 交叉编译工具链&#xff0c;终于编译通过了 下载 gcc 交叉编译工具链&#xff0c;这里推荐使用 arm 官方的 gcc 下载地址…

Apache Shiro 1.2.4反序列化漏洞(Shiro-550)--Shiro rememberMe反序列化漏洞(CVE-2016-4437)

前言 Apache Shiro是一款开源安全框架&#xff0c;提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用&#xff0c;同时也能提供健壮的安全性。 Apache Shiro 1.2.4及以前版本中&#xff0c;加密的用户信息序列化后存储在名为remember-me的Cookie中。攻击者可以使用S…

sort、uniq、tr、cut命令的使用

sort、uniq、tr、cut命令的使用 一、sort二、uniq三、tr四、cut 一、sort sort是一个以行为单位对文件内容排序的工具&#xff0c;也可以根据不同的数据类型来排序&#xff0c;例如数据和字符的排序就不一样。比较原则是从首字符向后&#xff0c;依次按ASCII码进行比较&#x…

MySQL-索引(1)

本文主要梳理的内容 : 主键索引的基本概念InnoDB引擎中的索引策略索引的分类(各种索引讲解)索引的创建索引的底层数据结构 目录 主键 InnoDB主键生成策略 数据库自增主键可能遇到什么问题. 如何选择合适的分布式主键方案呢&#xff1f; 超键、候选键、主键、外键分别是什…

第一个Mapreduce程序-wordcount

一个Maprduce程序主要包括三部分&#xff1a;Mapper类、Reducer类、执行类。 Maven项目下所需依赖 <dependencies><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-client</artifactId><version>3.3.0</v…

前端011_标签模块_列表功能

标签模块_列表功能 1、需求分析2、Mock添加数据列表模拟接口3、Api调用接口4、列表模版5、分页查询实现1、需求分析 标签模块主要文章标签进行管理,类别和标签的关系是一对多,一个类别下面存在多个标签。 首先开发模块中的列表功能,包含数据列表、分页。 2、Mock添加数据…

golang基于FFmpeg实现视频H264编解码

文章目录 一、基本知识1.1 FFmpeg相关1.2 H.264相关1.3 YUV相关 二、H264编码原理2.1 帧类型分析2.2 帧内/帧间预测2.3 变换量化2.4 滤波2.5 熵编码 三、H264解码为YUV3.1 代码逻辑及使用API3.2 具体代码实现3.3 YUV文件播放 四、YUV编码为H2644.1 代码逻辑及使用API4.2 具体代…

Python基础入门编程代码练习(二)

一、求1~100之间不能被3整除的数之和 循环条件&#xff1a;i<100循环操作 实现代码如下&#xff1a; def sums():sum 0for num in range(1, 101):if num % 3 ! 0:sum numprint("1~100之间不能被3整除的数之和为&#xff1a;%s" % (sum))sums() print("1~…

测试 —— 基础概念、开发模型、测试模型、BUG的描述

目录 一、什么是软件测试&#xff1f; 1. 调试和测试的区别 2. 优秀的测试人员需要具备的哪些素质 二、基本名词的概念 1. 什么是需求&#xff1f; 2. 什么是BUG&#xff1f; 3. 什么是测试用例&#xff1f; 4. 软件的生命周期&#xff1f;软件测试的生命周期&#xff1…

实验六 触发器与存储过程

实验六 触发器与存储过程 目录 实验六 触发器与存储过程 1、SQL触发器&#xff1a;删除学生数据题目代码题解 2、SQL触发器&#xff1a;创建成绩表插入触发器题目代码题解 3、 SQL存储过程&#xff1a;查询订单题目代码题解 4、SQL存储过程&#xff1a;建立存储过程&#xff0c…

ESP32设备驱动-Si4703调频收音机模块驱动

Si4703调频收音机模块驱动 文章目录 Si4703调频收音机模块驱动1、Si4703介绍2、硬件准备3、软件准备4、驱动实现1、Si4703介绍 Si4702/03 FM 无线电接收器系列通过小尺寸和电路板面积、最少的组件数量、灵活的可编程性以及卓越的、经过验证的性能,增加了向移动设备添加 FM 无…

4。计算机组成原理(5)总线和I/O

嵌入式软件开发&#xff0c;非科班专业必须掌握的基本计算机知识 核心知识点&#xff1a;数据表示和运算、存储系统、指令系统、总线系统、中央处理器、输入输出系统 一 总线概述 总线是一组能实现多个部件间信息传输的线路 按功能分类 1&#xff09;片内总线&#xff08;片…

【iOS】多线程以及GCD和NSOperation

iOS多线程 线程基础进程与线程NSThread类 GCD认识GCD任务队列队列与任务结合线程间通信队列组dispatch group延迟执行操作 NSOperation简介基本使用NSOperation、NSOperationQueue 常用属性和方法归纳NSOperation 常用属性和方法NSOperationQueue 常用属性和方法 小结 线程基础…

网易Android framework开发岗面试经历分享(附面试题汇总+案例解析)

背景 今年可以说是非常难&#xff0c;部门被拆&#xff0c;很多同事都被变相裁员了&#xff0c;虽然说去其他部门工作可以给我们的放宽一点要求&#xff0c;但我还是想在Android开的岗位上发展&#xff0c;所以也提出了离职&#xff1b;离职后的两个月中&#xff0c;投了一些A…

类ChatGPT逐行代码解读(1/2):从零实现Transformer、ChatGLM-6B

前言 最近一直在做类ChatGPT项目的部署 微调&#xff0c;关注比较多的是两个&#xff1a;一个LLaMA&#xff0c;一个ChatGLM&#xff0c;会发现有不少模型是基于这两个模型去做微调的&#xff0c;说到微调&#xff0c;那具体怎么微调呢&#xff0c;因此又详细了解了一下微调代…

tensorflow GPU训练环境布置

tensorflow GPU训练环境布置 一、显卡驱动安装1.1 如何处理**Failed to initialize NVML: Driver/library version mismatch的问题**1.2 卸载旧的版本1.3 驱动安装 1.3.1 利用apt 安装1.3.2 手动安装 二、安装CUDA2.1 确定CUDA版本2.2 下载文件1. 找匹配版本2. 选合适的平台 2…

微服务---Redis实用篇-黑马头条项目-商户查询缓存功能(缓存穿透,缓存雪崩,缓存击穿问题及解决思路)

1、商户查询缓存 1.1 什么是缓存? 前言:什么是缓存? 就像自行车,越野车的避震器 举个例子:越野车,山地自行车,都拥有"避震器",防止车体加速后因惯性,在酷似"U"字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样; 同样,实际开发中,系统也需要"避震…