SpringBoot 结合RabbitMQ与Redis实现商品的并发下单【SpringBoot系列12】
SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。
程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发
1 项目准备
- SpringBoot 整合 RabbitMQ 消息队列【SpringBoot系列11】本文章 基于这个项目来开发
本文章是系列文章 ,每节文章都有对应的代码,每节的源码都是在上一节的基础上配置而来,对应的视频讲解课程正在火速录制中。
订单系统,用户下单,即要保存即时性,也要保证流畅性,同时还要防止超卖,本文章是基于 RabbitMQ 消息队列 + Redis 实现的下单,当然后续还会的秒杀系统设计 以及后续的微服务以及熔断控制等等
如这里 我的商品 库存有 10 个

然后我使用 apache-jmeter-5.5 压测,200个用户1秒内请求完成,每个用户请求2次,也就是1秒有400次下单请求

测试完成后,商品库存为0,然后订单生成10个,完美解决并发问题

这是实现的普通订单,基本实现逻辑是
1、redis 校验库存,预下单
2、消息队列减库存 生成 订单 (数据库、redis、es)
3、用户查询到订单成功,发起支付
4、支付回调 修改订单数据 (数据库、redis 、es)
1 预下单接口
@Api(tags="订单模块")
@RestController()
@RequestMapping("/orders")
@Slf4j
public class OrderController {@Autowiredprivate OrderService orderService;/*** 下单* @param goodsId 商品ID* @param userId* @return*/@GetMapping("/create/{id}")public R createOrder(@PathVariable("id") Long goodsId,@RequestHeader Long userId) {return orderService.createPreOrder(goodsId,userId);}
}
@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate OrderMQSender mqSender;@Overridepublic R createPreOrder(Long goodsId, Long userId) {log.info("预下单处理 userId:{} goodsId:{} ",userId,goodsId);//获取redis中的商品库存 先判断商品是否有库存Boolean aBoolean = redisTemplate.hasKey("goodStock:" + goodsId);if(Boolean.FALSE.equals(aBoolean)){return R.error("下单失败 商品库存不足");}//获取商品库存int goodsStock = Integer.valueOf(redisTemplate.opsForValue().get("goodStock:" +goodsId).toString());if(goodsStock==0){return R.error("下单失败 商品库存不足");}//发送下单消息SecKillMessage message = new SecKillMessage(userId, goodsId);mqSender.sendCommonOrderMessage(JsonUtils.toJson(message));return R.okData("预下单成功");}
redisTemplate 的 hasKey 可以直接判断key是否存在,在这里如果商品的key不存在,则商品无库存,redis 的商品库存是在服务启动后,自动同步进入的
@Service
@Slf4j
public class OrderServiceImpl implements OrderService , InitializingBean {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate GoodsService goodsService;/*** 初始化秒杀商品数量到 redis 中** @return*/@Overridepublic R startSeckillInit() {List<SeckillGoods> goods = secKillGoodsService.findAllSecKillGoods();if (CollectionUtils.isEmpty(goods)) {return R.error("无秒杀商品");}goods.forEach(g -> {log.info("初始化秒杀商品 goodsId:{} stock: {}", g.getGoodsId(), g.getStockCount());redisTemplate.opsForValue().set("goodStock:" + g.getGoodsId(), g.getStockCount());});return R.ok("初始化完成");}@Overridepublic void afterPropertiesSet() throws Exception {this.startSeckillInit();}
InitializingBean 当一个类实现这个接口之后,Spring启动后,初始化Bean时,若该Bean实现InitialzingBean接口,会自动调用afterPropertiesSet()方法,完成一些用户自定义的初始化操作。
2 消息队列的定义
在这里单独定义普通下单使用的队列与交换机
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class OrderRabbitMQTopicConfig {private static final String commonOrderQueue = "commonOrderQueue";private static final String commonExchange = "commonOrderExchange";@Beanpublic Queue commonOrderQueue() {return new Queue(commonOrderQueue);}@Beanpublic TopicExchange commonExchange() {return new TopicExchange(commonExchange);}@Beanpublic Binding commonOrderBinding() {return BindingBuilder.bind(commonOrderQueue()).to(commonExchange()).with("commonOrder.#");}
}
然后就是订单的发送者
@Service
@Slf4j
public class OrderMQSender {@Autowiredprivate RabbitTemplate rabbitTemplate;/*** 普通订单走的队列* @param msg*/public void sendCommonOrderMessage(String msg) {log.info("预下单发送消息:{}", msg);rabbitTemplate.convertAndSend("commonOrderExchange", "commonOrder.message", msg);}
}
然后定义普通订单的消息接收者
@Service
@Slf4j
public class OrderMQReceiver {@Autowiredprivate OrderService orderService;@RabbitListener(queues = "commonOrderQueue")public void receiveCommonOrderMessage(String message) {log.info("接收的秒杀订单消息:{}", message);SecKillMessage secKillMessage = JsonUtils.toObj(message, SecKillMessage.class);Long userId = secKillMessage.getUserId();Long goodsId = secKillMessage.getGoodsId();//普通下单orderService.createOrder(goodsId, userId);}}
普通下单里,就是减库存,生成订单的过程
@Override@Transactionalpublic R createOrder(Long goodsId, Long userId) {log.info("下单处理 userId:{} goodsId:{} ",userId,goodsId);//查询商品详情Goods goods = goodsService.findGoods(goodsId);//商品的实际库存if (goods.getGoodsStock() < 1) {// 设置该商品库存为空redisTemplate.opsForValue().set("goodStock:" + goods.getId(), "0");log.info("库存不足 下单失败");return R.error("商品库存不足");}//减库存 int currentStock = goods.getGoodsStock() -1;//更新数据库 库存goods.setGoodsStock(currentStock);int update = goodsService.updateGoodsStock(goods);if(update<=0){log.info("更新库存失败 下单失败");return R.error("商品库存不足");}//更新redis 缓存redisTemplate.opsForValue().set("goodStock:" + goods.getId(), currentStock);// 下订单Order order = new Order();order.setUserId(userId);order.setGoodsId(goodsId);order.setDeliveryAddrId(0L);order.setGoodsName(goods.getGoodsName());order.setGoodsCount(1);order.setGoodsPrice(goods.getGoodsPrice());order.setOrderChannel(1);order.setStatus(0); // 订单创建中order.setCreateDate(new Date());orderMapper.insert(order);log.info("下单成功 userId:{} goodsId:{} orderId:{}",userId,goodsId,order.getId());//缓存普通订单redisTemplate.opsForValue().set("order:" +userId + ":" + goodsId, order);//保存数据到ES中//后续实现return R.okData(order);}
本文章是系列文章 ,每节文章都有对应的代码,每节的源码都是在上一节的基础上配置而来,对应的视频讲解课程正在火速录制中。
本文章只有核心代码,全部代码请查看对应源码
项目源码在这里 :https://gitee.com/android.long/spring-boot-study/tree/master/biglead-api-10-seckill
有兴趣可以关注一下公众号:biglead
- 创建SpringBoot基础项目
- SpringBoot项目集成mybatis
- SpringBoot 集成 Druid 数据源【SpringBoot系列3】
- SpringBoot MyBatis 实现分页查询数据【SpringBoot系列4】
- SpringBoot MyBatis-Plus 集成 【SpringBoot系列5】
- SpringBoot mybatis-plus-generator 代码生成器 【SpringBoot系列6】
- SpringBoot MyBatis-Plus 分页查询 【SpringBoot系列7】
- SpringBoot 集成Redis缓存 以及实现基本的数据缓存【SpringBoot系列8】
- SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】
- SpringBoot Security认证 Redis缓存用户信息【SpringBoot系列10】
- SpringBoot 整合 RabbitMQ 消息队列【SpringBoot系列11】
相关文章:
SpringBoot 结合RabbitMQ与Redis实现商品的并发下单【SpringBoot系列12】
SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。 程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发 1 项目准备 SpringBoot 整合 RabbitMQ 消息队…...
【python进阶】序列切片还能这么用?切片的强大比你了解的多太多
📚引言 🙋♂️作者简介:生鱼同学,大数据科学与技术专业硕士在读👨🎓,曾获得华为杯数学建模国家二等奖🏆,MathorCup 数学建模竞赛国家二等奖🏅,…...
[数据结构]直接插入排序、希尔排序
文章目录排序的概念和运用排序的概念排序运用常见的排序算法常见的排序算法直接插入排序希尔排序性能对比排序的概念和运用 排序的概念 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操…...
CNN、LeNet、AlexNet、VGG、GoogLeNet、RCNN、Fast RCNN、Faster RCNN、YOLO、YOLOv2、SSD等的关系
卷积神经网络的现状1943年美国数学家提出人工智能1949年心理学家建立神经元模型1957年弗兰克提出 感知器人工神经网络模型1980年建立多层感知器模型1984日本学者提出卷积神经网络原始模型神经感知机1998年提出LeNet-5卷积神经网络,并发展了其在音符和字符上的优势20…...
IO-day1-(fscanf、fprintf.........)
作业一、有一个usr.txt的文件,其中存储着用户的账户和密码,格式如下:zhangsan aaaalisi bbbbb空格前面是账户,空格后面是密码,一行一个账户、密码要求如下:从终端获取一个账户名和密码判断是否能够登录成功…...
C++类和对象(上篇)
目录 1.类的定义 2.类的访问限定符及封装 2.1类的访问限定符 2.2封装 3.类的作用域 4.类的实例化 5.类的大小 6.this 指针 1.类的定义 class className {// 类体:由成员函数和成员变量组成}; // 一定要注意后面的分号 class为定义类的关键字,Clas…...
解决Xshell无法连接Kali Linux 2020.1(2019.3)版本
使用Xshell远程终端工具连接虚拟机的Kali Linux却提示连接不上原因:Kali Linux默认没有打开SSH远程登录,SSH就是一种网络协议,用于加密的远程登录,所以在没有打开SSH协议之前是无法使用Xshell连接Kali Linux的。解决办法ÿ…...
项目文章 | 缓解高胆固醇血症 ,浒苔多糖如何相助?
文章标题:Polysaccharides from Enteromorpha prolifera alleviate hypercholesterolemia via modulating the gut microbiota and bile acid metabolism 发表期刊:Food & Function 影响因子:6.317 作者单位:福建医科大…...
Linux使用宝塔面板搭建网站,并内网穿透实现公网访问
文章目录前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4.固定http地址5. 配置二级子域名6.创建一个测试页面前言 宝塔面板作为简单好用的服务器运维管理面板,它支持Linux/Windows系统,我们可用它来一键配置LAMP/LNMP环境、网站、数据库、FTP等&…...
基于深度学习方法与张量方法的图像去噪相关研究
目录 1 研究现状 1.1 基于张量分解的高光谱图像去噪 1.2 基于深度学习的图像去噪算法 1.3 基于深度学习的高光谱去噪 1.4 小结 2 基于深度学习的图像去噪算法 2.1 深度神经网络基本知识 2.2 基于深度学习的图像去噪网络 2.3 稀疏编码 2.3.1 传统稀疏编码 2.3.2 群稀…...
Java基础知识之HashMap的使用
一、HashMap介绍 HashMap是Map接口的一个实现类(HashMap实现了Map的接口),它具有Map的特点。HashMap的底层是哈希表结构。 Map是用于保存具有映射关系的数据集合,它具有双列存储的特点,即一次必须添加两个元素…...
面试--每日一经
操作系统 死锁 死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 死锁的四个必要条件 互斥条件:一个资源每次只能被一个进…...
JavaSE进阶之(十六)枚举
十六、枚举16.1 背景16.2 枚举类型16.3 EnumSet 和 EnumMap01、EnumSet02、EnumMap16.1 背景 在 Java 语言中还没有引入枚举类型之前,表示枚举类型的常用模式是声明一组 int 类型的常量,常常用的就是: public static final int SPRING 1; …...
全同态加密:TFHE
参考文献: Cheon J H, Stehl D. Fully homomophic encryption over the integers revisited[C]//Advances in Cryptology–EUROCRYPT 2015: 34th Annual International Conference on the Theory and Applications of Cryptographic Techniques, Sofia, Bulgaria, …...
【计算机二级】综合题目
计算机二级python真题 文章目录计算机二级python真题一、《大学慕课 两问 》二、综合应用题——价值链三、基本操作题 ——信息输出一、《大学慕课 两问 》 附件中的文件data.txt 是教育部爱课程网中国大学MOOC平台的某个 HTML页面源文件,里面包含了我国参与MOOC建设的一批大学…...
初识Kafka
介绍 Kafka Kafka 是一款基于发布与订阅的消息系统。 用生产者客户端 API 向 Kafka 生产消息,用消费者客户端 API 从 Kafka 读取这些消息。 Kafka 使用 Zookeeper 保存元数据信息。 Kafka 0.9 版本之前,除了 broker 之外, 消费者也会使用…...
【JavaEE】线程的状态
哈喽,大家好~我是保护小周ღ,本期为大家带来的是 Java 多线程的 线程的状态,New 新建状态,Runnable 运行状态,Blocked 阻塞状态,waiting 等待状态,Time_Waiting 超时等待状态,Termin…...
7个最受瞩目的 Python 库,提升你的开发效率
当今时代,数据分析和处理已经成为了各行各业中不可或缺的一环。Python作为一种非常流行的编程语言,为我们提供了许多强大的工具和库来处理不同类型的数据。 在这篇文章中,我将向您介绍七个非常有用的Python库,这些库各自有着独特…...
这些IT行业趋势,将改变2023
上一周,你被"AI"刷屏了吗? 打开任何一家科技媒体,人工智能都是不变的热门话题。周初大家还在用ChatGPT写论文、查资料、写代码,到周末的时候大家已经开始用GPT-4图像识别来做饭、Microsoft 365 Copilot 来写PPT了。 GP…...
蓝桥杯每日一真题——[蓝桥杯 2021 省 B] 杨辉三角形(二分+规律)
文章目录[蓝桥杯 2021 省 B] 杨辉三角形题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示思路:全部代码:[蓝桥杯 2021 省 B] 杨辉三角形 题目描述 下面的图形是著名的杨辉三角形: 如果我们按从上到下、从左到右的顺序把所有数排成一列&…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
