Redis实现-优惠卷秒杀(基础版本)

在这里插入图片描述

(一)全局唯一ID

一、全局ID生成器

在这里插入图片描述
可以看到在优惠卷订单表中的主键id并没有设置Auto increment自增长
在这里插入图片描述
假如未来订单量达到数亿单,单表无法保存如此多数据,就需要对其进行分表存储(分布式)。假如每张表都采用自增长,各自从1开始自增,那么这个id就一定会出现重复,而从业务角度考虑,订单id都应当是独立且唯一的,那么也就会出现危险。也就需要去用到全局ID生成器。
在这里插入图片描述

  1. 唯一性
    Redis当中的String数据结构有一种自增且唯一特性的命令increm。因为Redis是独立于MySQL数据库之外的,无论有几个不同数据库几张不同的表,Redis都只有一个,这时当所有人来访问Redis时,它的自增ID一定是唯一的。
  2. 高可用
    将来讲解Redis的集群方案、主从方案、哨兵方案都可以确保Redis的高可用性。
  3. 高性能
    Redis的数据就是存储在内存当中,是以高性能著称的。
  4. 递增性
    Redis的自增方案就能保证数据的递增性、连续性。
  5. 安全性
    假如Redis直接采用这种低端自增方案,那么就会与MySQL数据库一样不存在安全性(因为太容易被猜测出规律),所以在使用increm全局自增ID时不能直接把这个数值用来当作ID,而是拼接其他信息来减弱规律性。
    在这里插入图片描述
    为了提高数据库性能,id会采用数值类型(也就是java中的Long型)。时间戳用于增加ID复杂性,长度为31位是因为将来要以秒为单位,定义一个初始时间至当下时间的时间差,31位能够存储21亿个单位,也就是接近69年,已经足够我们使用了。假如在一秒内生成多个订单需要生成多个ID,那么就会去自增后面的32位序列号,也就是Redis自增的值。
  • Redis并不是全局唯一ID的唯一实现方案,还会有很多其他的方案。

二、Redis实现全局唯一ID

在这里插入图片描述
在这里插入图片描述

  1. 为什么需要引入一个keyPrefix参数:
    该ID的生成策略是基于Redis自增长的,而我们也需要有一个key来对应该值,不同业务会有不同的key也就不能都去使用同一个自增长ID,也就是需要有一个前缀来区分不同业务。
  2. id生成策略也就是我们的核心业务-生成时间戳与序列号
    在这里插入图片描述
  3. 为什么需要在key中拼接动态日期字符串:
    若将key值写死,那么就代表整个业务永远采用同一个key来做自增长,也就是说无论该业务是经历了多少年,使用的永远是同一个key值,随着业务逐渐发展,值会越来越大。而我们知道,Redis中该自增值的上限是2的64次方,虽然该值看似足够大永远不会触及,但是它也是永远存在这个上限的,而且在key的生成策略当中用于记录序列号的值只有32个bit,这个值是很有可能被超过的。所以说我们不能永远使用同一个key,可以考虑在key值中拼接上当天的日期值,这样可以实现每天刷新value值上限,同时也更方便统计每天的记录数量。
  4. 为什么要将timestamp左移并且与count值进行或运算
    因为我们要想生成的全局id的最后32位都应当为从Redis当中得到的自增值,而当前仅有的是时间戳值,所以我们需要将时间戳值左移32位来空出摆放自增值的空间。并且因为或运算的效果是只要当前位置上的数字不为0即可直接赋值,所以这里与count值进行或运算就是相当于将count值直接赋值到序列号位的数字上,就能实现一个拼接的效果。

三、单元测试

测试在并发情况下生成id的性能以及值的情况,并且记录运行时间,因为使用的线程池是异步的,需要用CountDownLatch来记录每一条线程的执行时间并打印总耗费时间。
在这里插入图片描述
在这里插入图片描述
最终得到生成的全局ID以及总耗费时间,并且可以在Redis中查看到生成的id个数达30000个:
在这里插入图片描述
在这里插入图片描述

四、总结

在这里插入图片描述

(二)实现优惠卷秒杀下单

一、添加优惠卷

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、实现秒杀下单

在这里插入图片描述
在这里插入图片描述
优惠卷订单表:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(三)超卖问题

一、库存超卖问题分析

假设数据库中某优惠券的库存为100张,当我们使用jmeter调用200个线程来进行并发购买优惠券时会发现执行异常率为45%,也就是说有超过100条线程执行成功了,数据库中优惠券库存也变为负数,说明此时出现了超卖问题。
在这里插入图片描述

(1)执行流程分析
  1. 正常逻辑
    在这里插入图片描述
  2. 交叉执行
    在这里插入图片描述
(2)解决方案

悲观锁与乐观锁并不是一种真正的锁,而是一种锁的设计理念
在这里插入图片描述

  1. 悲观锁
    悲观锁认为一定会出现线程安全问题,因此会在操作数据之前先去获取锁,来确保所有线程串行执行,减少并发情况。但是这也代表它的性能并不是很好,因为所有线程都是一个一个去执行的,不适合高并发场景。
  2. 乐观锁
    乐观锁认为不一定会出现线程安全问题(认为出现问题的概率比较低),因此不会直接加锁,而是会在线程对数据做修改时去判断在这之前是否有别的线程已经对数据进行修改。也就是当我们查询到数据库中数据且将要对其做修改之前,会去检查当前被修改的数据是否与一开始查询到的数据相同,若不同则说明有别人已经对该数据进行修改了,会有线程安全问题,此时可以去重试或抛异常。它的性能会比悲观锁强很多,核心就是要去判断数据是否被修改。
(3)乐观锁
  1. 版本号法
    版本号法也就是给数据加上一个版本,在多线程并发时基于版本号来判断是否被修改过,每当数据做一次修改,版本号就会加一。要想判断一个数据是否被修改过,就是要判断它的版本号是否有变化。
    在这里插入图片描述
  2. CAS法
    直接将目标修改的数据值作为比较值,替代版本号的作用
    在这里插入图片描述

二、乐观锁解决超卖问题

  1. 乐观锁实现逻辑与业务逻辑的冲突问题
    在乐观锁中要求对数据修改前原数据不能发生变化,而在当前业务中仅要求库存大于0即可,乐观锁与业务的逻辑差异会导致线程异常率增大,也就是实际扣减成功率太低了,所以要对其进行优化。
    在这里插入图片描述
    在这里插入图片描述
  2. 最终代码修改情况
    在这里插入图片描述
    再次执行测试,发现线程异常率达50%,也就是正好卖空全部库存
    在这里插入图片描述
    在这里插入图片描述
  3. 总结
    在这里插入图片描述

(四)一人一单

一、一人一单功能实现

在这里插入图片描述
在这里插入图片描述
但是此时的业务逻辑并不完美,因为此时一人一单的逻辑与之前下单的逻辑相同,都是先查询再判断,这使得同样会出现多个线程穿插执行的情况,导致出现超卖,也就是并发安全问题。
之前提到的乐观锁是在更新数据时去使用的,这里是需要去插入数据,所以不能直接去判断数据是否有被修改过,而是要去判断数据是否存在,也就只能去使用悲观锁。

(1)具体改造流程
  1. 封装方法
    在这里插入图片描述
    在这里插入图片描述
  2. 根据用户id进行加锁并且通过获取代理对象来开启createVoucherOrder方法的事务
    在这里插入图片描述
    引入依赖并添加注解去暴露代理对象
    在这里插入图片描述
    在这里插入图片描述
  3. 测试
    在这里插入图片描述
    在这里插入图片描述

二、集群下的线程并发安全问题

在这里插入图片描述

  1. 正常执行情况
    在这里插入图片描述
  2. 交叉执行情况(出现并发问题)
    在这里插入图片描述
    会导致插入了两次订单
  3. 加入互斥锁的执行情况
    在这里插入图片描述
    产生线程安全问题的原因:
    在集群部署模式或分布式系统下,每一台服务都会有一个独立的JVM,而每个JVM当中都会存在独立的锁监视器去维护互斥锁,导致了每台服务中都有一个线程是能获取到互斥锁的,也就会发生并行运行,就可能出现线程安全问题。

解决办法:让多个JVM只能使用同一把锁,也就是实现跨进程的分布式锁

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

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

相关文章

c++STL——哈希表封装:实现高效unordered_map与unordered_set

文章目录 用哈希表封装unordered_map和unordered_set改进底层框架迭代器实现实现思路迭代器框架迭代器重载operator哈希表中获取迭代器位置 哈希表的默认成员函数修改后的哈希表的代码封装至上层容器 用哈希表封装unordered_map和unordered_set 在前面我们已经学过如何实现哈希…

虹科应用 | 探索PCAN卡与医疗机器人的革命性结合

随着医疗技术的不断进步,医疗机器人在提高手术精度、减少感染风险以及提升患者护理质量方面发挥着越来越重要的作用。医疗机器人的精确操作依赖于稳定且高效的数据通信系统,虹科提供的PCAN四通道mini PCIe转CAN FD卡,正是为了满足这一需求而设…

Yolov8的详解与实战-深度学习目标检测

Yolov8的详解与实战- 文章目录 摘要 模型详解 C2F模块 Loss head部分 模型实战 训练COCO数据集 下载数据集 COCO转yolo格式数据集(适用V4,V5,V6,V7,V8) 配置yolov8环境 训练 测试 训练自定义数据集 Labelme…

scons user 3.1.2

前言 感谢您抽出时间阅读有关 SCons 的内容。SCons 是一款下一代软件构建工具,或者称为 make 工具,即一种用于构建软件(或其他文件)并在底层输入文件发生更改时使已构建的软件保持最新状态的软件实用程序。 SCons 最显著的特点是…

Java的多线程笔记

创建一个线程的方法有多种,比如可以继承Thread类或者实现Runnable接口,结论是实现Runnable接口比前者更加优越。 二者代码对比 Java 不支持多继承,如果你继承了 Thread 类,就不能再继承其他类,实现 Runnable 接口后&am…

PDF Base64格式字符串转换为PDF文件临时文件

需求描述: 在对接电子病历系统与河北CA,进行免密文件签章的时候,两者系统入参不同,前者是pdf文件,base64格式;后者要求File类型的PDF文件。 在业务中间层开发时,则需要接收EMR侧提供的base64格式…

代码随想录训练营第二十三天| 572.另一颗树的子树 104.二叉树的最大深度 559.N叉树的最大深度 111.二叉树的最小深度

572.另一颗树的子树: 状态:已做出 思路: 这道题目当时第一时间不是想到利用100.相同的树思路来解决,而是先想到了使用kmp,不过这个题目官方题解确实是有kmp解法的,我使用的暴力解法,kmp的大致思…

【RabbitMq C++】消息队列组件

RabbitMq 消息队列组件 1. RabbitMq介绍2. 安装RabbitMQ3. 安装 RabbitMQ 的 C客户端库4. AMQP-CPP 库的简单使用4.1 使用4.1.1 TCP 模式4.1.2 扩展模式 4.2 常用类与接口介绍4.2.1 Channel4.3.2 ev 5. RabbitMQ样例编写5.1 发布消息5.2 订阅消息 1. RabbitMq介绍 RabbitMq - …

鸿蒙NEXT开发动画案例8

1.创建空白项目 2.Page文件夹下面新建Spin.ets文件,代码如下: /*** SpinKit动画组件 (重构版)* author: CSDN-鸿蒙布道师* since: 2025/05/14*/interface AnimationGroup {indexes: number[];delay: number; }ComponentV2 export struct SpinEight {Re…

MySQL全局优化

目录 1 硬件层面优化 1.1 CPU优化 1.2 内存优化 1.3 存储优化 1.4 网络优化 2 系统配置优化 2.1 操作系统配置 2.2 MySQL服务配置 3 库表结构优化 4 SQL及索引优化 mysql可以从四个层面考虑优化,分别是 硬件系统配置库表结构SQL及索引 从成本和优化效果来看&#xf…

vue和springboot交互数据,使用axios【跨域问题】

vue和springboot交互数据,使用axios【跨域问题】 提示:帮帮志会陆续更新非常多的IT技术知识,希望分享的内容对您有用。本章分享的是node.js和vue的使用。前后每一小节的内容是存在的有:学习and理解的关联性。【帮帮志系列文章】&…

FFMPEG 与 mp4

1. FFmpeg 中的 start_time 与 time_base start_time 流的起始时间戳(单位:time_base),表示第一帧的呈现时间(Presentation Time)。通常用于同步多个流(如音频和视频)。 time_base …

AI世界的崩塌:当人类思考枯竭引发数据生态链断裂

AI世界的崩塌:当人类思考枯竭引发数据生态链断裂 ——论过度依赖AI创作对技术进化的反噬 一、数据生态的恶性循环:AI的“自噬危机” 当前AI模型的训练依赖于人类创造的原始数据——书籍、论文、艺术作品、社交媒体动态等。据统计,2025年全球…

C++【STL】(2)string

C【STL】string用法扩展 1. assign:为字符串赋新值 用于替换字符串内容,支持多种参数形式。 常用形式: // 用另一个字符串赋值 str.assign("Hello World");// 用另一个字符串的子串(从第6个字符开始,取5…

树莓派4基于Debian GNU/Linux 12 (Bookworm)开启VNC,使用MobaXterm连接VNC出现黑屏/灰屏问题

1. 开启树莓派的VNC服务 启用VNC服务:通过raspi-config开启 # 1. 通过 raspi-config 工具开启 sudo raspi-config选择 Interface Options → VNC → Yes退出时会自动启动服务 检查服务状态: sudo systemctl status vncserver-x11-serviced正常输出应显示…

MongoDB使用x.509证书认证

文章目录 自定义证书生成CA证书生成服务器之间的证书生成集群证书生成用户证书 MongoDB配置java使用x.509证书连接MongoDBMongoShell使用证书连接 8.0版本的mongodb开启复制集,配置证书认证 自定义证书 生成CA证书 生成ca私钥: openssl genrsa -out ca…

Python爬虫实战:研究js混淆加密

一、引言 在当今数字化时代,数据已成为推动各行业发展的核心驱动力。网络爬虫作为一种高效的数据采集工具,能够从互联网上自动获取大量有价值的信息。然而,随着互联网技术的不断发展,许多网站为了保护自身数据安全和知识产权,采用了 JavaScript 混淆加密技术来防止数据被…

Java项目层级介绍 java 层级 层次

java 层级 层次 实体层 控制器层 数据连接层 Service : 业务处理类 Repository :数据库访问类 Java项目层级介绍 https://blog.csdn.net/m0_67574906/article/details/145811846 在Java项目中,层级结构(Layered Architecture&#xf…

网络安全顶会——SP 2025 论文清单与摘要

1、"Check-Before-you-Solve": Verifiable Time-lock Puzzles 时间锁谜题是一种密码学原语,它向生成者保证该谜题无法在少于T个顺序计算步骤内被破解。近年来,该技术已在公平合约签署和密封投标拍卖等场景中得到广泛应用。然而,求解…

《100天精通Python——基础篇 2025 第18天:正则表达式入门实战,解锁字符串处理的魔法力量》

目录 一、认识正则表达式二、正则表达式基本语法2.1 行界定符2.2 单词定界符2.3 字符类2.4 选择符2.5 范围符2.6 排除符2.7 限定符2.8 任意字符2.9 转义字符2.10 反斜杠2.11 小括号2.11.1 定义独立单元2.11.2 分组 2.12 反向引用2.13 特殊构造2.14 匹配模式 三、re模块3.1 comp…