通过异步使用消息队列优化秒杀
通过异步使用消息队列优化秒杀
- 同步秒杀流程
- 异步优化秒杀
- 异步秒杀流程
- 基于lua脚本保证Redis操作原子性
- 代码实现
- 阻塞队列的缺点
同步秒杀流程
public Result seckillVoucher(Long voucherId) throws InterruptedException {SeckillVoucher seckillVoucher = iSeckillVoucherService.getById(voucherId);LocalDateTime beginTime = seckillVoucher.getBeginTime();if (LocalDateTime.now().isBefore(beginTime)) {return Result.fail("秒杀还未开始");}LocalDateTime endTime = seckillVoucher.getEndTime();if (LocalDateTime.now().isAfter(endTime)) {return Result.fail("秒杀已经结束");}
// 查看库存Integer stock = seckillVoucher.getStock();if (stock < 1) {return Result.fail("库存不足");}Long userId = UserHolder.getUser().getId();
// redis集群实现RLock lock = redissonClient.getLock("order:" + userId);boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS);if (!isLock) {return Result.fail("你已经购买优惠卷");}try {IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId, userId);} finally {
// redisLock.releaseLock();lock.unlock();}}@Transactionalpublic Result createVoucherOrder(Long voucherId, Long userId) {Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if (count > 0) {return Result.fail("已经购买优惠卷");}
// 基于乐观锁避免超卖问题,在乐观锁的基础上做出了改进,当要修改库存时判断库存是否大于0,但是也给数据库带来了巨大的压力boolean success = iSeckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id", voucherId).gt("stock", 0).update();if (!success) {return Result.fail("库存不足");}VoucherOrder voucherOrder = new VoucherOrder();long orderId = redisWorker.getGlobalId("order");voucherOrder.setId(orderId);voucherOrder.setVoucherId(voucherId);voucherOrder.setUserId(userId);save(voucherOrder);return Result.ok(orderId);}
以上是我们同步秒杀的流程,首先会从数据库中查询优惠卷,检查库存和是否在秒杀阶段,然后使用redisson基于Redis实现分布式锁解决一人一单问题,如果都通过后就会修改库存并且生成订单,整体的流程如下:
在这个流程中,我们发现整个步骤都是同步,都是交给主线程来执行,负责检验库存保证一人一单然后来修改数据库数据,我们是否可以再请一个人来优化这个流程呢?比如A来负责检验,检验成功后交给B来完成数据库的修改,这样是不是能够优化秒杀的业务
异步优化秒杀
我们可以这样做,让主线程来负责判断这个人能不能抢杀,如果抢杀成功交给另一个人来负责修改数据库,主线程直接返回订单id。
那怎样让主线程快速的库存和一人一单的判断呢?Redis
我们可以将库存保存在Redis的字符串类型中,Redis的性能高于直接查询数据库的性能,而一人一单的校验我们可以使用Redis的set集合,校验是否一人一单就看Redis的set集合中是否含有用户id,而当主线程通过校验后让Redis中的库存-1并且将用户id添加到set集合中。
而我们怎样让修改数据库的任务交给另一个人来处理呢?使用阻塞队列或者消息队列,为了实现简单,暂且使用阻塞队列,我们将要修改数据库的任务放入阻塞队列中,创建一个线程池,让线程池来负责执行修改数据库的任务。
异步秒杀流程
从流程中看出我们主线程只负责进行库存校验和确保一人一单后,生成订单添加到阻塞队列或消息队列中就直接返回,大大提高了业务的性能。
基于lua脚本保证Redis操作原子性
如果我们使用以上的操作来实现代码,会发生并发安全问题,因为校验Redis操作和更新Redis操作不具有原子性,就可能发生问题,例如一个线程通过校验Redis库存等还没有进行修改,另一个线程直接进入比较没有修改的库存,就发生了线程安全问题,我们需要保证这些操作具有原子性
代码实现
lua脚本:
--优惠卷id
local voucherId = ARGV[1];
--用户id
local userId = ARGV[2];
--库存键
local stockKey = "voucher:stock:" + voucherId;
--下过订单的用户键
local orderKey = "voucher:order:" + voucherId;
--查看卷库存是否足够
if (tonumber(redis.call("get", stockKey)) <= 0) then
return 1;
end
if (redis.call("sismember",orderKey,userId)==1) then
return 2
end
--扣库存
redis.call("incrby",stockKey,-1);
--添加用户id
redis.call("sadd",orderKey,userId)
return 0
/*** 异步秒杀*/@Overridepublic Result seckillVoucher(Long voucherId) throws InterruptedException {Long userId = UserHolder.getUser().getId();Long result = redisTemplate.execute(SECkILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString());int re = result.intValue();if (re != 0) {return re == 1 ? Result.fail("库存不足") : Result.fail("不能重复下单");}long orderId = redisWorker.getGlobalId("voucher:order");VoucherOrder voucherOrder = new VoucherOrder();voucherOrder.setVoucherId(voucherId);voucherOrder.setId(orderId);voucherOrder.setUserId(userId);VOUCHER_PROXY = (IVoucherOrderService) AopContext.currentProxy();BLOCKING_QUEUE.add(voucherOrder);return Result.ok(orderId);}
// 初始化lua脚本环境private static final DefaultRedisScript<Long> SECkILL_SCRIPT;static {SECkILL_SCRIPT = new DefaultRedisScript<>();SECkILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECkILL_SCRIPT.setResultType(Long.class);}// 在类初始化完之后执行@PostConstructprivate void init() {EXECUTOR_SERVICE.execute(new VoucherHandler());}
// 阻塞队列获取任务处理private class VoucherHandler implements Runnable {@Overridepublic void run() {while (true){try {
// 从阻塞队列中获取订单信息VoucherOrder voucherOrder = BLOCKING_QUEUE.take();
// 更新数据库库存以及创建优惠卷订单VOUCHER_PROXY.createVoucherOrder(voucherOrder.getVoucherId(), voucherOrder.getUserId());} catch (InterruptedException e) {e.printStackTrace();}}}}@Transactionalpublic Result createVoucherOrder(Long voucherId, Long userId) {Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if (count > 0) {return Result.fail("已经购买优惠卷");}
// 基于乐观锁避免超卖问题,在乐观锁的基础上做出了改进,当要修改库存时判断库存是否大于0,但是也给数据库带来了巨大的压力boolean success = iSeckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id", voucherId).gt("stock", 0).update();if (!success) {return Result.fail("库存不足");}VoucherOrder voucherOrder = new VoucherOrder();long orderId = redisWorker.getGlobalId("order");voucherOrder.setId(orderId);voucherOrder.setVoucherId(voucherId);voucherOrder.setUserId(userId);save(voucherOrder);return Result.ok(orderId);}
阻塞队列的缺点
在我们进行数据库的修改任务时我们使用了阻塞队列来实现,在实际的业务中,我们需要使用一些消息队列来代替阻塞队列,阻塞队列使用的是JVM中的内存,当消息过多时会造成JVM内存爆满,并且功能不够强大,我们可以使用Redis的Stream或者MQ来实现消息队列来代替阻塞队列
相关文章:

通过异步使用消息队列优化秒杀
通过异步使用消息队列优化秒杀 同步秒杀流程异步优化秒杀异步秒杀流程基于lua脚本保证Redis操作原子性代码实现阻塞队列的缺点 同步秒杀流程 public Result seckillVoucher(Long voucherId) throws InterruptedException {SeckillVoucher seckillVoucher iSeckillVoucherServi…...

AI产业告别“独奏”时代,“天翼云息壤杯”高校AI大赛奏响产学研“交响乐”
文 | 智能相对论 作者 | 陈泊丞 人工智能产业正在从“独奏”时代进入“大合奏”时代。 在早期的AI发展阶段,AI应用主要集中在少数几个领域,如语音识别、图像处理等。这些领域的研究和开发工作往往由少数几家公司或研究机构即可独立完成,犹…...

Hot100 - 字母异位词分组
Hot100 - 字母异位词分组 最佳思路:排序 时间复杂度: O(nmlogm),其中 n 为 strs 数组的长度,m 为每个字符串的长度。 代码: class Solution {public List<List<String>> groupAnagrams(String[] strs) …...

力扣hot100-->排序
排序 1. 56. 合并区间 中等 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 示例 1: 输…...

【VRChat 全身动捕】VIVE 手柄改 tracker 定位器教程,低成本光学动捕解决方案(持续更新中2024.11.26)
更新 0.0.1(2024/11/26): 1.解决了内建蓝牙无法识别、“steamVR 蓝牙不可用” 的解决方案 2.解决了 tracker 虽然建立了连接但是在 steamVR 界面上看不到的问题 3.解决了 VIVE 基站1.0 无法被蓝牙识别 && 无法被 steamVR 搜索到 &…...

【Nginx】核心概念与安装配置解释
文章目录 1. 概述2. 核心概念2.1.Http服务器2.2.反向代理2.3. 负载均衡 3. 安装与配置3.1.安装3.2.配置文件解释3.2.1.全局配置块3.2.2.HTTP 配置块3.2.3.Server 块3.2.4.Location 块3.2.5.upstream3.2.6. mine.type文件 3.3.多虚拟主机配置 4. 总结 1. 概述 Nginx是我们常用的…...

Qt界面篇:QMessageBox高级用法
1、演示效果 2、用法注意 2.1 设置图标 用于显示实际图标的pixmap取决于当前的GUI样式。也可以通过设置icon pixmap属性为图标设置自定义pixmap。 QMessageBox::Icon icon(...
【二叉树】【2.1遍历二叉树】【刷题笔记】【灵神题单】
关注二叉树的三个问题: 什么情况适合自顶向下?什么时候适合用自底向上?一般来说,DFS的递归边界是空节点,什么情况下要额外把叶子节点作为递归边界?在什么情况下,DFS需要有返回值?什…...
Mongo数据库 --- Mongo Pipeline
Mongo数据库 --- Mongo Pipeline 什么是Mongo PipelineMongo Pipeline常用的几个StageExplanation with example:MongoDB $matchMongoDB $projectMongoDB $groupMongoDB $unwindMongoDB $countMongoDB $addFields Some Query Examples在C#中使用Aggreagtion Pipeline**方法一: …...

Adobe Illustrator 2024 安装教程与下载分享
介绍一下 下载直接看文章末尾 Adobe Illustrator 是一款由Adobe Systems开发的矢量图形编辑软件。它广泛应用于创建和编辑矢量图形、插图、徽标、图标、排版和广告等领域。以下是Adobe Illustrator的一些主要特点和功能: 矢量绘图:Illustrator使用矢量…...
javax.xml.ws.soap.SOAPFaultException: ZONE_OFFSET
javax.xml.ws.soap.SOAPFaultException 表示 SOAP 调用过程中发生了错误,并且服务端返回了一个 SOAP Fault。 错误信息中提到的 ZONE_OFFSET 可能指的是时区偏移量。在日期和时间处理中,时区偏移量是指格林威治标准时间 (GMT) 的偏移量。如果服务期望特…...
常用的数据结构
队列(FIFO) 栈(LIFO) 链表 hash表 hash冲突处理 开放式寻址 线性探测 表示依次检查索引为 hash(key) + 1、hash(key) + 2 ... 的位置。i 是冲突后的探查步数。公式:hash(i) = (hash(key) + i) % TableSize二次探查 规则:冲突后探查的步长是平方递增的,例如,检查位置为 hash…...

javaweb-day01-html和css初识
html:超文本标记语言 CSS:层叠样式表 1.html实现新浪新闻页面 1.1 标题排版 效果图: 1.2 标题颜色样式 1.3 标签内颜色样式 1.4设置超链接 1.5 正文排版 1.6 页面布局–盒子 (1)盒子模型 (2)页面布局…...

C++11特性(详解)
目录 1.C11简介 2.列表初始化 3.声明 1.auto 2.decltype 3.nullptr 4.范围for循环 5.智能指针 6.STL的一些变化 7.右值引用和移动语义 1.左值引用和右值引用 2.左值引用和右值引用的比较 3.右值引用的使用场景和意义 4.右值引用引用左值及其一些更深入的使用场景分…...

基于Springboot的心灵治愈交流平台系统的设计与实现
基于Springboot的心灵治愈交流平台系统 介绍 基于Springboot的心灵治愈交流平台系统,后端框架使用Springboot和mybatis,前端框架使用Vuehrml,数据库使用mysql,使用B/S架构实现前台用户系统和后台管理员系统,和不同级别…...

初识java(2)
大家好,今天我们来讲讲java中的数据类型。 java跟我们的c语言的数据类型有一些差别,那么接下来我们就来看看。 一.字面常量,其中:199,3.14,‘a’,true都是常量将其称为字面常量。(…...

AIGC--AIGC与人机协作:新的创作模式
AIGC与人机协作:新的创作模式 引言 人工智能生成内容(AIGC)正在以惊人的速度渗透到创作的各个领域。从生成文本、音乐、到图像和视频,AIGC使得创作过程变得更加快捷和高效。然而,AIGC并非完全取代了人类的创作角色&am…...

Wonder3D本地部署到算家云搭建详细教程
Wonder3D简介 Wonder3D仅需2至3分钟即可从单视图图像中重建出高度详细的纹理网格。Wonder3D首先通过跨域扩散模型生成一致的多视图法线图与相应的彩色图像,然后利用一种新颖的法线融合方法实现快速且高质量的重建。 本文详细介绍了在算家云搭建Wonder3D的流程以及…...
【设计模式】【行为型模式(Behavioral Patterns)】之状态模式(State Pattern)
1. 设计模式原理说明 状态模式(State Pattern) 是一种行为设计模式,它允许对象在其内部状态发生变化时改变其行为。这个模式的核心思想是使用不同的类来表示不同的状态,每个状态类都封装了与该状态相关的特定行为。当对象的状态发…...

QML学习 —— 34、视频媒体播放器(附源码)
效果 说明 您可以单独使用MediaPlayer播放音频内容(如音频),也可以将其与VideoOutput结合使用以渲染视频。VideoOutput项支持未转换、拉伸和均匀缩放的视频演示。有关拉伸均匀缩放演示文稿的描述,请参见fillMode属性描述。 播放可能出错问题 出现的问题: DirectS…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...

华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...