当前位置: 首页 > article >正文

Redis项目:秒杀业务(优化)

当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤

1、查询优惠卷

2、判断秒杀库存是否足够

3、查询订单

4、校验是否是一人一单

5、扣减库存

6、创建订单

在这六步操作中,又有很多操作是要去操作数据库的,而且还是一个线程串行执行, 这样就会导致我们的程序执行的很慢,所以我们需要异步程序执行

思路:

当用户下单之后,判断库存是否充足只需要到redis中去根据key找对应的value是否大于0即可,如果不充足,则直接结束,如果充足,继续在redis中判断用户是否可以下单,如果set集合中没有这条数据,说明他可以下单,如果set集合中没有这条记录,则将userId和优惠卷存入到redis中,并且返回0,整个过程需要保证是原子性的,我们可以使用lua来操作

当以上判断逻辑走完之后,我们可以判断当前redis中返回的结果是否是0 ,如果是0,则表示可以下单,则将之前说的信息存入到到queue中去,然后返回,然后再来个线程异步的下单,前端可以通过返回的订单id来判断是否下单成功

执行lua脚本 用redis判断购买资格  0则表示用户有资格 消息已经发出

-- 1、参数列表
-- 优惠卷id
local voucherId = ARGV[1]
-- 用户id
local userId = ARGV[2]
-- 订单id
local orderId = ARGV[3]-- 2、数据key
-- 库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 订单key
local orderKey = 'seckill:order:' .. voucherId-- 3、脚本业务
-- 获取库存
local stock = redis.call('get', stockKey)
-- 判断库存是否存在或是否为空
if (stock == nil or stock == '') then-- 库存不存在或为空,返回错误码(例如 3 表示库存未初始化)return 1
end
-- 将库存转换为数字
local stockNum = tonumber(stock)
-- 判断库存是否为有效数字
if (stockNum == nil) then-- 库存不是有效数字,返回错误码(例如 4 表示库存数据异常)return 1
end
-- 判断库存充足与否
if (stockNum <= 0) then-- 库存不足return 1
end
-- 判断用户是否下单
if (redis.call('sismember', orderKey, userId) == 1) then-- 重复下单return 2
end
-- 扣库存 保存用户
redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, userId)
-- 发送消息到队列中
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
return 0
public Result seckillVoucher(Long voucherId) {//获取用户 线程Long userId = UserHolder.getUser().getId();//执行LUA脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPY,Collections.emptyList(),voucherId.toString(), userId.toString());int r = result.intValue();//判断结果是不是0if (r != 0) {//不是0 代表没有购买资格return Result.fail(r == 1 ? "库存不足" : "不能重复下单");}//是0 有购买资格,保存下单消息到阻塞队列中// TODO 保存阻塞队列VoucherOrder voucherOrder = new VoucherOrder();// 订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 用户idvoucherOrder.setUserId(userId);// 代金券idvoucherOrder.setVoucherId(voucherId);//创建阻塞队列orderTasks.add(voucherOrder);//获取代理对象 放入队列proxy = (IVoucherOrderService) AopContext.currentProxy();//返回订单idreturn Result.ok(orderId);}*

优化:基于阻塞队列实现秒杀优化

//异步处理线程池
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
//在类初始化之后执行,因为当这个类初始化好了之后,随时都是有可能要执行的
@PostConstruct
private void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}
// 用于线程池处理的任务
// 当初始化完毕后,就会去从对列中去拿信息private class VoucherOrderHandler implements Runnable{@Overridepublic void run() {while (true){try {// 1.获取队列中的订单信息VoucherOrder voucherOrder = orderTasks.take();// 2.创建订单handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("处理订单异常", e);}}}private void handleVoucherOrder(VoucherOrder voucherOrder) {//1.获取用户Long userId = voucherOrder.getUserId();// 2.创建锁对象RLock redisLock = redissonClient.getLock("lock:order:" + userId);// 3.尝试获取锁boolean isLock = redisLock.lock();// 4.判断是否获得锁成功if (!isLock) {// 获取锁失败,直接返回失败或者重试log.error("不允许重复下单!");return;}try {//注意:由于是spring的事务是放在threadLocal中,此时的是多线程,事务会失效proxy.createVoucherOrder(voucherOrder);} finally {// 释放锁redisLock.unlock();}}//aprivate BlockingQueue<VoucherOrder> orderTasks =new  ArrayBlockingQueue<>(1024 * 1024);@Overridepublic Result seckillVoucher(Long voucherId) {Long userId = UserHolder.getUser().getId();long orderId = redisIdWorker.nextId("order");// 1.执行lua脚本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString(), String.valueOf(orderId));int r = result.intValue();// 2.判断结果是否为0if (r != 0) {// 2.1.不为0 ,代表没有购买资格return Result.fail(r == 1 ? "库存不足" : "不能重复下单");}VoucherOrder voucherOrder = new VoucherOrder();// 2.3.订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 2.4.用户idvoucherOrder.setUserId(userId);// 2.5.代金券idvoucherOrder.setVoucherId(voucherId);// 2.6.放入阻塞队列orderTasks.add(voucherOrder);//3.获取代理对象proxy = (IVoucherOrderService)AopContext.currentProxy();//4.返回订单idreturn Result.ok(orderId);}@Transactionalpublic  void createVoucherOrder(VoucherOrder voucherOrder) {Long userId = voucherOrder.getUserId();// 5.1.查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();// 5.2.判断是否存在if (count > 0) {// 用户已经购买过了log.error("用户已经购买过了");return ;}// 6.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1") // set stock = stock - 1.eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0) // where id = ? and stock > 0.update();if (!success) {// 扣减失败log.error("库存不足");return ;}save(voucherOrder);}

代码逻辑及实现

1、使用 Executors.newSingleThreadExecutor() 创建一个单线程的线程池,用于异步处理订单

2、执行lua 判断是不是满足要求 满足要求 提交到线程池中 
         proxy = (IVoucherOrderService)AopContext.currentProxy();获取代理 存储到成员变量中,以便后续在异步线程中使用。

3、返回给用户已经完成

4、之后的就是在线程池中完成
        int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count(); 这里做数据库二次检查,确保数据一致性(Redis 和数据库可能不同步) 

总结思路

  • 先利用Redis完成库存余量、一人一单判断,完成抢单业务

  • 再将下单业务放入阻塞队列,利用独立线程异步下单

相关文章:

Redis项目:秒杀业务(优化)

当用户发起请求&#xff0c;此时会请求nginx&#xff0c;nginx会访问到tomcat&#xff0c;而tomcat中的程序&#xff0c;会进行串行操作&#xff0c;分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单 4、校验是否是一人一单 5、扣减库存 6、创建订单…...

《Gradio Python 客户端入门》

《Gradio Python 客户端入门》 Gradio Python 客户端使将任何 Gradio 应用程序用作 API 变得非常容易。例如&#xff0c;考虑这个 Hugging Face Space&#xff0c;它转录从麦克风录制的音频文件。 使用该库&#xff0c;我们可以轻松地将 Gradio 用作 API 以编程方式转录音频文…...

仿函数 VS 函数指针实现回调

前提&#xff1a; 本博客对比 函数指针实现回调 和 仿函数 &#xff0c;突出仿函数的优势。 目的&#xff1a; 一个类要能够灵活的调用两个函数&#xff0c;essfc 和 greaterfc&#xff0c;分别用于比较两个整数的大小&#xff1a; ①&#xff1a;lessfc&#xff1a;判断 x …...

MQTT的安装和使用

MQTT的安装和使用 在物联网开发中&#xff0c;mqtt几乎已经成为了广大程序猿必须掌握的技术&#xff0c;这里小编和大家一起学习并记录一下~~ 一、安装 方式1、docker安装 官网地址 https://www.emqx.com/zh/downloads-and-install/broker获取 Docker 镜像 docker pull e…...

网络工程师考试详细介绍,讲解,备考方案

一、考试科目与形式 1. 科目1&#xff1a;基础知识&#xff08;计算机与网络知识&#xff09; - 考试形式&#xff1a;机考&#xff0c;75道选择题&#xff08;含5道英文题&#xff09;&#xff0c;满分75分 - 核心内容&#xff1a; - 计算机系统&#xff1a;硬件组成&…...

ROS melodic 安装 python3 cv_bridge

有时候&#xff0c;我们需要处理这些兼容性问题。此处列举我的过程&#xff0c;以供参考 mkdir -p my_ws_py39/src cd my_ws_py39 catkin_make_isolated-DPYTHON_EXECUTABLE/usr/bin/python3 \-DPYTHON_INCLUDE_DIR/usr/include/python3.8 \-DPYTHON_LIBRARY/usr/lib/x86_64-l…...

SHELL练习01

判断一个数是奇数还是偶数 要求&#xff1a; 编写一个 Shell 脚本&#xff0c;用户输入一个整数&#xff0c;判断该数是奇数还是偶数&#xff0c;并输出结果。 [rootnode test01]# touch Determine parity.sh [rootnode test01]# vim Determine parity.sh 还有 2 个文件等待…...

PRODIGY: “不折腾人”的蛋白-蛋白/蛋白-小分子结合能计算工具

PRODIGY&#xff08;全称为 PROtein binDIng enerGY prediction&#xff09;是一种蛋白质结合能预测工具&#xff0c;可利用蛋白质-蛋白质复合物的三维结构来预测其结合亲和力。PRODIGY 利用一种高效的基于接触的方法&#xff0c;在估计结合自由能和解离常数的同时&#xff0c;…...

C++之 【模板初阶(函数模板与类模板)】

目录 1.泛型编程 2.模板 3函数模板 3.1函数模板的概念 3.2函数模板的格式 3.3函数模板的原理 3.4函数模板的实例化 3.4.1隐式实例化&#xff1a;让编译器根据实参推演模板参数的实际类型 3.4.2显示实例化&#xff1a;在函数名后的<>中指定模板参数的实际类型 3.…...

博弈论中的均衡精炼:完美贝叶斯均衡、序贯均衡与颤抖手均衡详解

博弈论中的均衡精炼&#xff1a;完美贝叶斯均衡、序贯均衡与颤抖手均衡详解 1. 引言&#xff1a;为什么需要均衡精炼&#xff1f; 在博弈论中&#xff0c;纳什均衡是分析策略互动的核心工具&#xff0c;但其存在一个显著缺陷&#xff1a;无法排除不合理的均衡。例如&#xff0…...

在线教育网站项目第四步:deepseek骗我, WSL2不能创建两个独立的Ubuntu,但我们能实现实例互访及外部访问

一、说明 上一章折腾了半天&#xff0c;搞出不少问题&#xff0c;今天我们在deepseek的帮助下&#xff0c;完成多个独立ubuntu24.04实例的安装&#xff0c;并完成固定ip&#xff0c;实践证明&#xff0c;deepseek不靠谱&#xff0c;浪费我2个小时时间&#xff0c;我们将在下面实…...

在刀刃上发力:如何精准把握计划关键节点

关键路径分析是项目管理中的一种重要方法&#xff0c;它通过在甘特图中识别出项目中最长、最关键的路径&#xff0c;来确定项目的最短完成时间。 关键路径上的任务都是项目成功的关键因素&#xff0c;任何延误都可能导致整个项目的延期。关键路径分析对于项目管理者来说至关重要…...

组合总和||

1.给定一个数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用一次。 #include <bits/stdc.h> using namespace std; vector<vector<int>> result; vec…...

OpenCV图像拼接(2)基于羽化(feathering)技术的图像融合算法拼接类cv::detail::FeatherBlender

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::detail::FeatherBlender 是 OpenCV 中用于图像拼接的一个类&#xff0c;它属于 stitching 模块的一部分。这个类实现了基于羽化&#xff08;…...

MediaPipe软件包如何构建和安装

MediaPipe 是一个由 Google 开发的多媒体机器学习框架&#xff0c;支持多种平台&#xff08;如 Android、iOS、桌面等&#xff09;。以下是构建和安装 MediaPipe 的步骤&#xff1a; 1. 环境准备 确保系统满足以下要求&#xff1a; 操作系统: Ubuntu (推荐 18.04 或 20.04)、…...

分享下web3j 常见用法

转账 fun sendEthTransaction(privateKey: String,toAddress: String,amount: BigDecimal) {//chainIdval chainId:Long 1//url 可以从https://chainlist.org/里面获取可用节点//eth转账&#xff0c;bnb同理&#xff0c;但需发送到bnb对应节点val url "https://xxx"…...

连接chatgpt的桌面语音助手

要创建一个连接到 ChatGPT 的桌面语音助手&#xff0c;可以使用 Python 编写一个程序来实现语音识别、与 ChatGPT API 交互以及语音合成的功能。以下是一个完整的解决方案和技术实现步骤&#xff1a; 所需工具和库 语音识别 使用 speech_recognition 库捕获用户的语音输入。需要…...

systemctl restart 和 systemctl reload 和 systemctl daemon-reload 对比 笔记250322

systemctl restart 和 systemctl reload 和 systemctl daemon-reload 对比 以下是 systemctl restart、systemctl reload 和 systemctl daemon-reload 的对比总结&#xff1a; 命令作用对象行为适用场景对服务的影响systemctl restart 服务名具体服务强制停止服务&#xff0c…...

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能示例9,TableView15_09带排序的导出表格示例

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…...

spring boot 登入权限RBAC模式

首先准备好5张表 user_info表&#xff0c;用户的信息表 role表&#xff0c;角色表&#xff08;比如超级管理员、管理员、审核员、采购......&#xff09; 创建user_role表&#xff0c;user_info表&#xff0c;role表的中间表 注意了&#xff0c;role_id和user_id是 u…...

调用API拿到的值——存储方式

1.响应结果示例&#xff1a; "purposeTagList":["稳中向好及进中提质"] 2.数据库中定义的 3.值的获取&#xff1a; F1 JsonNode purposeTagListNode dataNode.path("purposeTagList");if (purposeTagListNode.isArray()) {StringBuilder purp…...

【用 Trace读源码】PlanAgent 执行流程

前提条件 在 Trae 中打开 OpenManus 工程&#xff0c;使用 build 模式&#xff0c;模型选择 claude-sonnet-3.7 提示词 分析 agent/planning.py 中 main 方法及相关类的执行流程&#xff0c;以流程图的方式展示PlanningAgent 执行流程图 以下流程图展示了 PlanningAgent 类…...

第一讲 | 解锁C++编程能力:基础语法解析

C入门基础 一、C的第一个程序二、命名空间三、C输入&输出四、缺省参数/默认参数五、函数重载六、引用1.引用的特性2.引用的使用引用做返回值场景 3.const引用只有指针和引用涉及权限放大、缩小的问题&#xff0c;普通变量没有 4.指针和引用的关系 七、inline八、nullptr 一…...

LeetCode 热题 100_划分字母区间(80_763_中等_C++)(贪心算法(求并集))

LeetCode 热题 100_划分字母区间&#xff08;80_763&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;贪心算法(求交集)&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;贪心算法(求…...

C++ --- 多态

1 多态的概念 多态(polymorphism)的概念&#xff1a;通俗来说&#xff0c;就是多种形态。多态分为编译时多态(静态多态)和运⾏时多 态(动态多态)&#xff0c;这⾥我们重点讲运⾏时多态&#xff0c;编译时多态(静态多态)和运⾏时多态(动态多态)。编译时 多态(静态多态)主要就是我…...

HAL库中使用空闲中断+DMA接收数据,接收失败的问题

问题&#xff1a; 串口屏与单片机通过串口&#xff08;USART1&#xff09;进行通信&#xff0c;调试时发现问题&#xff0c;现象如下&#xff1a; 手动页面的几个文本&#xff0c;输入的数字不会显示出来&#xff0c;比如初始值为0&#xff0c;输入200&#xff0c;200会一闪而…...

【STM32实物】基于STM32的扫地机器人/小车控制系统设计

基于STM32的扫地机器人/小车控制系统设计 演示视频: 基于STM32的扫地机器人小车控制系统设计 简介:扫地机器人系统采用分层结构设计,主要包括底层硬件控制层、中间数据处理层和上层用户交互层。底层硬件控制层负责对各个硬件模块进行控制和数据采集,中间数据处理层负责对采…...

【Scrapy】Scrapy教程8——处理子链接

通过前面几篇文章,已经了解了如何去爬取网页内容并存储到数据库,但是目前只是存储了一个页面的内容,现在想要获取每篇文章链接内的文章内容,我们来看看怎么获取。 生成新请求 首先我们肯定要先拿到链接,所以第一步都获取文章标题和链接肯定少不了,然后再爬取获取到到子…...

使用pycel将Excel移植到Python

1.适用需求 有些工作可能长期适用excel来进行公式计算&#xff0c;当需要把工作流程转换为可视化界面时&#xff0c;开发人员不懂专业逻辑&#xff0c;手动摸索公式很大可能出错&#xff0c;而且费时费力 2.可用工具及缺点 pandas 方便进行数据处理&#xff0c;支持各种格…...

学习应用层

应用层概述 客户/服务器方式&#xff08;C/S&#xff09;和对等方式&#xff08;P2P&#xff09; 动态主机配置协议DHCP 客户/服务器方式 DHCP报文会被封装成为UDP用户数据报&#xff0c;DHCP服务器端口号是UDP67&#xff0c;用户是UDP68。 广播发送&#xff0c;是因为并不知道…...