SpringBoot 结合RabbitMQ与Redis实现商品的并发下单【SpringBoot系列12】

SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。
程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发

1 项目准备

  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 {
    @Autowired
    private 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);
    }
}
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private OrderMQSender mqSender;
    @Override
    public 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 {
   
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private GoodsService goodsService;

    /**
     * 初始化秒杀商品数量到 redis 中
     *
     * @return
     */
    @Override
    public 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("初始化完成");
    }

    @Override
    public 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";

    @Bean
    public Queue commonOrderQueue() {
        return new Queue(commonOrderQueue);
    }
    @Bean
    public TopicExchange commonExchange() {
        return new TopicExchange(commonExchange);
    }
    @Bean
    public Binding commonOrderBinding() {
        return BindingBuilder.bind(commonOrderQueue()).to(commonExchange()).with("commonOrder.#");
    }
}

然后就是订单的发送者

@Service
@Slf4j
public class OrderMQSender {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * 普通订单走的队列
     * @param msg
     */
    public void sendCommonOrderMessage(String msg) {
        log.info("预下单发送消息:{}", msg);
        rabbitTemplate.convertAndSend("commonOrderExchange", "commonOrder.message", msg);
    }
}

然后定义普通订单的消息接收者

@Service
@Slf4j
public class OrderMQReceiver {

    @Autowired
    private 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
    @Transactional
    public 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


  1. 创建SpringBoot基础项目
  2. SpringBoot项目集成mybatis
  3. SpringBoot 集成 Druid 数据源【SpringBoot系列3】
  4. SpringBoot MyBatis 实现分页查询数据【SpringBoot系列4】
  5. SpringBoot MyBatis-Plus 集成 【SpringBoot系列5】
  6. SpringBoot mybatis-plus-generator 代码生成器 【SpringBoot系列6】
  7. SpringBoot MyBatis-Plus 分页查询 【SpringBoot系列7】
  8. SpringBoot 集成Redis缓存 以及实现基本的数据缓存【SpringBoot系列8】
  9. SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】
  10. SpringBoot Security认证 Redis缓存用户信息【SpringBoot系列10】
  11. SpringBoot 整合 RabbitMQ 消息队列【SpringBoot系列11】

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【前端八股文】浏览器系列:性能优化——HTML、CSS、JS、渲染优化

文章目录HTMLCSSCSS加载会造成阻塞吗JavaScript渲染优化参考本系列目录&#xff1a;【前端八股文】目录总结 是以《代码随想录》八股文为主的笔记。详情参考在文末。 代码随想录的博客_CSDN博客-leecode题解,ACM题目讲解,代码随想录领域博主 性能优化&#xff0c;从以下几个方…

【C++】STL容器、算法的简单认识

几种模板首先认识一下函数模板、类模板、栈模板。函数模板函数模板就是一个模型&#xff0c;而模板函数是函数模板经过类型实例化的函数。如下template<class T>是一个简单的函数模板&#xff1a;template<class T> T Max(T a, T b) {return a > b ? a : b; } …

Joomla未授权访问漏洞CVE-2023-23752

1、前言Joomla是一套全球知名的内容管理系统&#xff08;CMS&#xff09;&#xff0c;其使用PHP语言加上MySQL数据库所开发&#xff0c;可以在Linux、Windows、MacOSX等各种不同的平台上运行。2月16日&#xff0c;Joomla官方发布安全公告&#xff0c;修复了Joomla! CMS中的一个…

cjson文件格式介绍

cjson是一种轻量级的JSON解析库&#xff0c;它支持将JSON格式的数据转换为C语言中的数据结构&#xff0c;同时也支持将C语言中的数据结构转换为JSON格式的数据。cjson的文件格式是指在使用cjson库时&#xff0c;将JSON格式的数据存储在文件中&#xff0c;然后通过cjson库读取文…

C++ 学习笔记(十)(继承、抽象篇)

前言&#xff1a;主要是自己学习过程的积累笔记&#xff0c;所以跳跃性比较强&#xff0c;建议先自学后拿来作为复习用。 文章目录1 定义父类和子类1.1 定义父类访问说明符 protected1.2 定义子类1.3 子类向父类的转换1.4 转换的例外1.5 子类的构造函数1.6 静态成员不能继承1.7…

clip精读

开头部分 1. 要点一 从文章题目来看-目的是&#xff1a;使用文本监督得到一个可以迁移的 视觉系统 2.要点二 之前是 fix-ed 的class 有诸多局限性&#xff0c;所以现在用大量不是精细标注的数据来学将更好&#xff0c;利用的语言多样性。——这个方法在 nlp其实广泛的存在&…

2023年ACM竞赛班 2023.3.20题解

目录 瞎编乱造第一题 瞎编乱造第二题 瞎编乱造第三题 瞎编乱造第四题 瞎编乱造第五题 不是很想编了但还是得编的第六题 不是很想编了但还是得编的第七题 还差三道题就编完了的第八题 还差两道题就编完了的第九题 太好啦终于编完了 为啥一周六天早八阿 瞎编乱造第一题…

【Matlab算法】粒子群算法求解一维线性函数问题(附MATLAB代码)

MATLAB求解一维线性函数问题前言正文函数实现可视化处理可视化结果前言 一维线性函数&#xff0c;也称为一次函数&#xff0c;是指只有一个自变量xxx的函数&#xff0c;且函数表达式可以写成yaxbyaxbyaxb的形式&#xff0c;其中aaa和bbb是常数。具体来说&#xff0c;aaa称为斜…

typedef uint8_t u8;(stm32数据类型)

在stm32单片机的库文件里有这么一段u8和u16的定义 typedef uint8_t u8; typedef uint16_t u16&#xff1b; 而uint8_t和uint16_t的定义是这样的 typedef unsigned char uint8_t; typedef unsigned short int uint16_t; 意味着u8就是就是指代的unsigned char …

linux简单入门

目录Linux简介Linux目录结构Linux文件命令文件处理命令文件查看命令常用文件查看命令Linux的用户和组介绍Linux权限管理Linux简介 Linux&#xff0c;全称GNU/Linux&#xff0c;是一种免费使用和自由传播的类UNIX操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹&#xff0…

【Nginx二】——Nginx常用命令 配置文件

Nginx常用命令 配置文件常用命令启动和重启 Nginx配置文件maineventshttp常用命令 安装完成nginx后&#xff0c;输入 nginx -&#xff1f;查询nginx命令行参数 nginx version: nginx/1.22.1 Usage: nginx [-?hvVtTq] [-s signal] [-p prefix][-e filename] [-c filename] [-…

[数据结构]直接插入排序、希尔排序

文章目录排序的概念和运用排序的概念排序运用常见的排序算法常见的排序算法直接插入排序希尔排序性能对比排序的概念和运用 排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操…

FastApi快速构建一个web项目

FastApi快速构建一个web项目 已经使用FastApi很久了。这个一个非常优秀的框架。和flask一样能够快速构建一个web服务。开发效率非常之高。今天我一个Demo来介绍一下这个框架的使用。供大家学习参考。 项目介绍 本项目主要介绍fastapi快速编写web服务&#xff0c;通过案例分别…

贪心算法(一)

一、概念 贪心算法的核心思想是&#xff0c;在处理一个大问题时&#xff0c;划分为多个局部并在每个局部选择最优解&#xff0c;并且认为在每个局部选择最优解&#xff0c;那么最后全局的问题得到的就是最优解。 贪心算法可以解决一些问题&#xff0c;但是不适用于所有问题&a…

音乐制作:Ableton Live 11 Suite Mac

Ableton Live 11 Suite Mac是一款非常专业的音乐制作软件&#xff0c;Live 是用于音乐创作和表演的快速、流畅和灵活的软件。它带有效果、乐器、声音和各种创意功能;制作任何类型的音乐所需的一切。以传统的线性排列方式进行创作&#xff0c;或者在 Live 的 Session 视图中不受…

MyBatisPlus的Wrapper使用示例

一、wapper介绍 1、Wrapper家族 在MP中我们可以使用通用Mapper&#xff08;BaseMapper&#xff09;实现基本查询&#xff0c;也可以使用自定义Mapper&#xff08;自定义XML&#xff09;来实现更高级的查询。当然你也可以结合条件构造器来方便的实现更多的高级查询。 Wrappe…

【Spring6】| Spring IoC注解式开发

目录 一&#xff1a;Spring IoC注解式开发 1. 回顾注解 2. 声明Bean的四个注解 3. Spring注解的使用 4. 选择性实例化Bean 5. 负责注入的注解&#xff08;重点&#xff09; 5.1 Value 5.2 Autowired与Qualifier 5.3 Resource 6. 全注解式开发 一&#xff1a;Spring I…

Springboot+vue开发的图书借阅管理系统项目源码下载-P0029

前言图书借阅管理系统项目是基于SpringBootVue技术开发而来&#xff0c;功能相对比较简单&#xff0c;分为两个角色即管理员和学生用户&#xff0c;核心业务功能就是图书的发布、借阅与归还&#xff0c;相比于一些复杂的系统&#xff0c;该项目具备简单易入手&#xff0c;便于二…

基于深度学习的车型识别系统(Python+清新界面+数据集)

摘要&#xff1a;基于深度学习的车型识别系统用于识别不同类型的车辆&#xff0c;应用YOLO V5算法根据不同尺寸大小区分和检测车辆&#xff0c;并统计各类型数量以辅助智能交通管理。本文详细介绍车型识别系统&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的实现代码…

你掌握了吗?在PCB设计中,又快又准地放置元件

在印刷电路板设计中&#xff0c;设置电路板轮廓后&#xff0c;将零件(占地面积)调用到工作区。然后将零件重新放置到正确的位置&#xff0c;并在完成后进行接线。 组件放置是这项工作的第一步&#xff0c;对于之后的平滑布线工作是非常重要的工作。如果在接线工作期间模块不足…