广告
返回顶部
首页 > 资讯 > 后端开发 > Python >SpringBoot+RabbitMQ+Redis实现商品秒杀的示例代码
  • 763
分享到

SpringBoot+RabbitMQ+Redis实现商品秒杀的示例代码

2024-04-02 19:04:59 763人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录业务分析创建表功能实现1.用户校验2.下单3.减少库存4.支付总结业务分析 一般而言,商品秒杀大概可以拆分成以下几步: 用户校验 校验是否多次抢单,保证每个商品每个用户只能秒杀一

业务分析

一般而言,商品秒杀大概可以拆分成以下几步:

用户校验
校验是否多次抢单,保证每个商品每个用户只能秒杀一次

下单
订单信息进入消息队列,等待消费

减少库存
消费订单消息,减少商品库存,增加订单记录

付款
十五分钟内完成支付,修改支付状态

创建表

Goods_info 商品库存表

说明
id 主键(uuid)
goods_name 商品名称
goods_stock 商品库存

package com.jason.seckill.order.entity;



public class GoodsInfo {

    private String id;
    private String goodsName;
    private String goodsStock;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getGoodsName() {
        return goodsName;
    }

    public void setGoodsName(String goodsName) {
        this.goodsName = goodsName;
    }

    public String getGoodsStock() {
        return goodsStock;
    }

    public void setGoodsStock(String goodsStock) {
        this.goodsStock = goodsStock;
    }

    @Override
    public String toString() {
        return "GoodsInfo{" +
                "id='" + id + '\'' +
                ", goodsName='" + goodsName + '\'' +
                ", goodsStock='" + goodsStock + '\'' +
                '}';
    }
}

order_info 订单记录表

说明
id 主键(uuid)
user_id 用户id
goods_id 商品id
pay_status 支付状态(0-超时未支付 1-已支付 2-待支付)

package com.jason.seckill.order.entity;


public class OrderRecord {

    private String id;
    private String userId;
    private String goodsId;
    
    private Integer payStatus;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(String goodsId) {
        this.goodsId = goodsId;
    }

    public Integer getPayStatus() {
        return payStatus;
    }

    public void setPayStatus(Integer payStatus) {
        this.payStatus = payStatus;
    }

    @Override
    public String toString() {
        return "OrderRecord{" +
                "id='" + id + '\'' +
                ", userId='" + userId + '\'' +
                ", goodsId='" + goodsId + '\'' +
                '}';
    }
}

功能实现

1.用户校验

使用Redis做用户校验,保证每个用户每个商品只能抢一次,上代码:


public boolean checkSeckillUser(OrderRequest order) {
        String key = env.getProperty("seckill.redis.key.prefix") + order.getUserId() + order.getGoodsId();
        return redisTemplate.opsForValue().setIfAbsent(key,"1");
    }

userId+orderId的组合作为key,利用redis的setnx分布式原理来实现。如果是限时秒杀,可以通过设置key的过期时间来实现。

2.下单

下单信息肯定是要先扔到消息队列里的,这里采用RabbitMQ来做消息队列,先来看一下消息队列的模型图:


rabbitMQ的配置:


#rabbitmq配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.passWord=guest
#消费者数量
spring.rabbitmq.listener.simple.concurrency=5
#最大消费者数量
spring.rabbitmq.listener.simple.max-concurrency=10
#消费者每次从队列获取的消息数量。写多了,如果长时间得不到消费,数据就一直得不到处理
spring.rabbitmq.listener.simple.prefetch=1
#消费接收确认机制-手动确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual

mq.env=local
#订单处理队列
#交换机名称
order.mq.exchange.name=${mq.env}:order:mq:exchange
#队列名称
order.mq.queue.name=${mq.env}:order:mq:queue
#routingkey
order.mq.routing.key=${mq.env}:order:mq:routing:key

rabbitmq配置类OrderRabbitmqConfig:



@Configuration
public class OrderRabbitmqConfig {

    private static final Logger logger = LoggerFactory.getLogger(OrderRabbitmqConfig.class);


    @Autowired
    private Environment env;

    
    @Autowired
    private CachinGConnectionFactory connectionFactory;

    
    @Autowired
    private SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;

    
    @Bean
    public RabbitTemplate rabbitTemplate(){
        //消息发送成功确认,对应application.properties中的spring.rabbitmq.publisher-confirms=true
        connectionFactory.setPublisherConfirms(true);
        //消息发送失败确认,对应application.properties中的spring.rabbitmq.publisher-returns=true
        connectionFactory.setPublisherReturns(true);
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //设置消息发送格式为JSON
        rabbitTemplate.setMessageConverter(new Jackson2jsonMessageConverter());
        rabbitTemplate.setMandatory(true);
        //消息发送到exchange回调 需设置:spring.rabbitmq.publisher-confirms=true
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                logger.info("消息发送成功:correlationData({}),ack({}),cause({})",correlationData,ack,cause);
            }
        });
        //消息从exchange发送到queue失败回调  需设置:spring.rabbitmq.publisher-returns=true
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                logger.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message);
            }
        });
        return rabbitTemplate;
    }

    //---------------------------------------订单队列------------------------------------------------------

    
    @Bean("orderTopicExchange")
    public TopicExchange orderTopicExchange(){
        //设置为持久化 不自动删除
        return new TopicExchange(env.getProperty("order.mq.exchange.name"),true,false);
    }

    
    @Bean("orderQueue")
    public Queue orderQueue(){
        return new Queue(env.getProperty("order.mq.queue.name"),true);
    }

    
    @Bean
    public Binding simpleBinding(){
        return BindingBuilder.bind(orderQueue()).to(orderTopicExchange()).with(env.getProperty("order.mq.routing.key"));
    }

    
    @Autowired
    private OrderListener orderListener;

    
    @Bean("orderListenerContainer")
    public SimpleMessageListenerContainer orderListenerContainer(){
        //创建监听器容器工厂
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        //将配置信息和链接信息赋给容器工厂
        factoryConfigurer.configure(factory,connectionFactory);
        //容器工厂创建监听器容器
        SimpleMessageListenerContainer container = factory.createListenerContainer();
        //指定监听器
        container.setMessageListener(orderListener);
        //指定监听器监听的队列
        container.setQueues(orderQueue());
        return container;
    }
}

配置类声明了订单队列,交换机,通过指定的routingkey绑定了队列与交换机。另外,rabbitTemplate用来发送消息,ListenerContainer指定监听器(消费者)监听的队列。

客户下单,生产消息,上代码:


@Service
public class SeckillService {

    private static final Logger logger = LoggerFactory.getLogger(SeckillService.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private Environment env;

    
    public void seckill(OrderRequest order){
        //设置交换机
        rabbitTemplate.setExchange(env.getProperty("order.mq.exchange.name"));
        //设置routingkey
        rabbitTemplate.setRoutingKey(env.getProperty("order.mq.routing.key"));
        //创建消息体
        Message msg = MessageBuilder.withBody(JSON.toJSONString(order).getBytes()).build();
        //发送消息
        rabbitTemplate.convertAndSend(msg);
    }
}

很简单,操作rabbitTemplate,指定交换机和routingkey,发送消息到绑定的队列,等待消费处理。

3.减少库存

消费者消费订单消息,做业务处理。
看一下监听器(消费者)OrderListener:



@Component
public class OrderListener implements ChannelAwareMessageListener {

    private static final Logger logger = LoggerFactory.getLogger(OrderListener.class);

    @Autowired
    private OrderService orderService;
    
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        try{
            //获取交付tag
            long tag = message.getMessageProperties().getDeliveryTag();
            String str = new String(message.getBody(),"utf-8");
            logger.info("接收到的消息:{}",str);
            JSONObject obj = JSONObject.parseObject(str);
            //下单,操作数据库
            orderService.order(obj.getString("userId"),obj.getString("goodsId"));
            //确认消费
            channel.basicAck(tag,true);
        }catch(Exception e){
            logger.error("消息监听确认机制发生异常:",e.fillInStackTrace());
        }
    }
}

业务处理 OrderService:


@Service
public class OrderService {

    @Resource
    private SeckillMapper seckillMapper;

    
    @Transactional()
    public void order(String userId,String goodsId){
        //该商品库存-1(当库存>0时)
        int count = seckillMapper.reduceGoodsStockById(goodsId);
        //更新成功,表明抢单成功,插入下单记录,支付状态设为2-待支付
        if(count > 0){
            OrderRecord orderRecord = new OrderRecord();
            orderRecord.setId(CommonUtils.createUUID());
            orderRecord.setGoodsId(goodsId);
            orderRecord.setUserId(userId);
            orderRecord.setPayStatus(2);
            seckillMapper.insertOrderRecord(orderRecord);
        }
    }
}

Dao接口和mybatis文件就不往出贴了,这里的逻辑是,update goods_info set goods_stock = goods_stock-1 where goods_stock > 0 and id=#{goodsId},这条update相当于将查询库存和减少库存合并为一个原子操作,避免高并发问题,执行成功,插入订单记录,执行失败,则库存不够抢单失败。

4.支付

订单处理完成后,如果库存减少,也就是抢单成功,那么需要用户在十五分钟内完成支付,这块就要用到死信队列(延迟队列)来处理了,先看模型图:


DLX:dead-letter Exchange 死信交换机
DLK:dead-letter RoutingKey 死信路由
ttl:time-to-live 超时时间
死信队列中,消息到期后,会通过DLX和DLK进入到pay-queue,进行消费。这是另一组消息队列,和订单消息队列是分开的。这里注意他们的绑定关系,主交换机绑定死信队列,死信交换机绑定的是主队列(pay queue)。
接下来声明图中的一系列组件,首先application.properties中增加配置:


#支付处理队列
#主交换机
pay.mq.exchange.name=${mq.env}:pay:mq:exchange
#死信交换机(DLX)
pay.dead-letter.mq.exchange.name=${mq.env}:pay:dead-letter:mq:exchange
#主队列
pay.mq.queue.name=${mq.env}:pay:mq:queue
#死信队列
pay.dead-letter.mq.queue.name=${mq.env}:pay:dead-letter:mq:queue
#主routingkey
pay.mq.routing.key=${mq.env}:pay:mq:routing:key
#死信routingkey(DLK)
pay.dead-letter.mq.routing.key=${mq.env}:pay:dead-letter:mq:routing:key
#支付超时时间(毫秒)(TTL),测试原因,这里模拟5秒,如果是生产环境,这里可以是15分钟等
pay.mq.ttl=5000

配置类OrderRabbitmqConfig中增加支付队列和死信队列的声明:


    
    @Bean
    public Queue payDeadLetterQueue(){
        Map args = new HashMap();
        //声明死信交换机
        args.put("x-dead-letter-exchange",env.getProperty("pay.dead-letter.mq.exchange.name"));
        //声明死信routingkey
        args.put("x-dead-letter-routing-key",env.getProperty("pay.dead-letter.mq.routing.key"));
        //声明死信队列中的消息过期时间
        args.put("x-message-ttl",env.getProperty("pay.mq.ttl",int.class));
        //创建死信队列
        return new Queue(env.getProperty("pay.dead-letter.mq.queue.name"),true,false,false,args);
    }

    
    @Bean
    public TopicExchange payTopicExchange(){
        return new TopicExchange(env.getProperty("pay.mq.exchange.name"),true,false);
    }

    
    @Bean
    public Binding payBinding(){
        return BindingBuilder.bind(payDeadLetterQueue()).to(payTopicExchange()).with(env.getProperty("pay.mq.routing.key"));
    }

    
    @Bean
    public Queue payQueue(){
        return new Queue(env.getProperty("pay.mq.queue.name"),true);
    }

    
    @Bean
    public TopicExchange payDeadLetterExchange(){
        return new TopicExchange(env.getProperty("pay.dead-letter.mq.exchange.name"),true,false);
    }

    
    @Bean
    public Binding payDeadLetterBinding(){
        return BindingBuilder.bind(payQueue()).to(payDeadLetterExchange()).with(env.getProperty("pay.dead-letter.mq.routing.key"));
    }

    
    @Autowired
    private PayListener payListener;

    
    @Bean
    public SimpleMessageListenerContainer payMessageListenerContainer(){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factoryConfigurer.configure(factory,connectionFactory);
        SimpleMessageListenerContainer listenerContainer = factory.createListenerContainer();
        listenerContainer.setMessageListener(payListener);
        listenerContainer.setQueues(payQueue());
        return listenerContainer;
    }

支付队列和死信队列的Queue、Exchange、routingkey都已就绪。
看生产者:


@Service
public class OrderService {

    @Resource
    private SeckillMapper seckillMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private Environment env;

    
    @Transactional()
    public void order(String userId,String goodsId){
        //该商品库存-1(当库存>0时)
        int count = seckillMapper.reduceGoodsStockById(goodsId);
        //更新成功,表明抢单成功,插入下单记录,支付状态设为2-待支付
        if(count > 0){
            OrderRecord orderRecord = new OrderRecord();
            orderRecord.setId(CommonUtils.createUUID());
            orderRecord.setGoodsId(goodsId);
            orderRecord.setUserId(userId);
            orderRecord.setPayStatus(2);
            seckillMapper.insertOrderRecord(orderRecord);
            //将该订单添加到支付队列
            rabbitTemplate.setExchange(env.getProperty("pay.mq.exchange.name"));
            rabbitTemplate.setRoutingKey(env.getProperty("pay.mq.routing.key"));
            rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
            String json = JSON.toJSONString(orderRecord);
            Message msg = MessageBuilder.withBody(json.getBytes()).build();
            rabbitTemplate.convertAndSend(msg);
        }
    }
}

在OrderService中,数据库操作完成后,将订单信息发送到死信队列,死信队列中的消息会在十五分钟后进入到支付队列,等待消费。
再看消费者:


@Component
public class PayListener implements ChannelAwareMessageListener {

    private static final Logger logger = LoggerFactory.getLogger(PayListener.class);

    @Autowired
    private PayService payService;
    
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        Long tag = message.getMessageProperties().getDeliveryTag();
        try {
            String str = new String(message.getBody(), "utf-8");
            logger.info("接收到的消息:{}",str);
            JSONObject json = JSON.parseObject(str);
            String orderId = json.getString("id");
            //确认是否付款
            payService.confirmPay(orderId);
            //确认消费
            channel.basicAck(tag, true);
        }catch(Exception e){
            logger.info("支付消息消费出错:{}",e.getMessage());
            logger.info("出错的tag:{}",tag);
        }
    }
}

PayService:


@Service
public class PayService {

    private static final Logger logger = LoggerFactory.getLogger(PayService.class);

    @Resource
    private SeckillMapper seckillMapper;

    
    public void confirmPay(String orderId){
        OrderRecord orderRecord = seckillMapper.selectNoPayOrderById(orderId);
        //根据订单号校验该用户是否已支付
        if(checkPay(orderId)){
            //已支付
            orderRecord.setPayStatus(1);
            seckillMapper.updatePayStatus(orderRecord);
            logger.info("用户{}已支付",orderId);
        }else{
            //未支付
            orderRecord.setPayStatus(0);
            seckillMapper.updatePayStatus(orderRecord);
            //取消支付后,商品库存+1
            seckillMapper.returnStock(orderRecord.getGoodsId());
            logger.info("用户{}未支付",orderId);
        }
    }

    
    public boolean checkPay(String orderId){
        Random random = new Random();
        int res = random.nextInt(2);
        return res==0?false:true;
    }

这里checkPay()方法模拟调用第三方支付接口来判断用户是否已支付。若支付成功,订单改为已支付状态,支付失败,改为已取消状态,库存退回。

总结

整个demo,是两组消息队列撑起来的,一组订单消息队列,一组支付消息队列,而每一组队列都是由queue、exchange、routingkey、生产者以及消费者组成。交换机通过routingkey绑定队列,rabbitTemplate通过指定交换机和routingkey将消息发送到指定队列,消费者监听该队列进行消费。不同的是第二组支付队列里嵌入了死信队列来做一个十五分钟的延迟支付。

到此这篇关于SpringBoot+RabbitMQ+Redis实现商品秒杀的文章就介绍到这了,更多相关SpringBoot+RabbitMQ+Redis实现商品秒杀内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: SpringBoot+RabbitMQ+Redis实现商品秒杀的示例代码

本文链接: https://www.lsjlt.com/news/157020.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • SpringBoot+RabbitMQ+Redis实现商品秒杀的示例代码
    目录业务分析创建表功能实现1.用户校验2.下单3.减少库存4.支付总结业务分析 一般而言,商品秒杀大概可以拆分成以下几步: 用户校验 校验是否多次抢单,保证每个商品每个用户只能秒杀一...
    99+
    2022-11-12
  • springboot +rabbitmq+redis实现秒杀示例
    目录实现说明1、工具准备2、数据表3、pom4、代码结构5、配置config6、订单业务层7、redis实现层8、mq实现层9、redis模拟初始化库存量10、controller控...
    99+
    2022-11-13
  • 基于mysql乐观锁实现秒杀的示例代码
    目录说明具体实现代码实现说明 如果你的项目流量非常小,完全不用担心有并发的购买请求,那么做这样一个系统意义不大。但如果你的系统要像12306那样,接受高并发访问和下单的考验,那么你就需要一套完整的流程保护措施,来保证你系...
    99+
    2022-07-01
    mysql乐观锁秒杀 mysql秒杀
  • Springboot整合RabbitMQ实现发送验证码的示例代码
    目录1.RabbitMQ的介绍2.搭建环境2.1引入jar包2.2生产者配置2.2.1Rabbit配置类2.2.2application.yml文件配置2.3消费者配置2.3.1消费...
    99+
    2022-11-13
  • Springboot+Redis实现API接口限流的示例代码
    添加Redis的jar包. <dependency> <groupId>org.springframework.boot</groupId&...
    99+
    2022-11-12
  • SpringBoot+Redis实现布隆过滤器的示例代码
    目录简述Redis 安装 Bloom Filter基本指令结合 SpingBoot方式一方式二简述 关于布隆过滤器的详细介绍,我在这里就不再赘述一遍了 我们首先知道:BloomFil...
    99+
    2022-11-13
  • SpringBoot整合Redis实现访问量统计的示例代码
    目录前言Spring Boot 整合 Redis引入依赖、增加配置翠花!上代码前言 之前开发系统的时候客户提到了一个需求:需要统计某些页面的访问量,记得当时还纠结了一阵子,不知道怎么...
    99+
    2022-11-13
  • springboot+redis实现微博热搜排行榜的示例代码
    目录技术模拟思路:步骤1:先初始化1个月的历史数据步骤2:定时刷新数据步骤3:排行榜查询接口技术模拟思路: 采用26个英文字母来实现排行,随机为每个字母生成一个随机数作为score ...
    99+
    2022-11-13
  • SpringBoot结合Redis实现接口幂等性的示例代码
    目录介绍实现过程引入 maven 依赖spring 配置文件写入引入 Redis自定义注解token 的创建和实现拦截器的配置测试用例介绍 幂等性的概念是,任意多次执行所产生的影响都...
    99+
    2022-11-13
  • Springboot使用redis实现接口Api限流的示例代码
    前言 该篇介绍的内容如题,就是利用redis实现接口的限流(  某时间范围内 最大的访问次数 ) 。 正文  惯例,先看下我们的实战目录结构: 首先...
    99+
    2022-11-13
  • PHP实现RabbitMQ消息列队的示例代码
    目录业务场景1、首先部署好thinkphp6框架2、安装workerman扩展3、生产者4、消费者5、整体测试业务场景 项目公司是主php做开发的,框架为thinkphp。众所周知,...
    99+
    2022-11-13
  • SpringBoot+Redis实现消息的发布与订阅的示例代码
    目录1.什么是redis的发布与订阅2.Redis发布订阅3.命令行实现功能订阅主题模式匹配订阅发布消息取消订阅测试4.SpringBoot实现功能Springboot整合Redis...
    99+
    2022-11-13
  • springboot+redis 实现分布式限流令牌桶的示例代码
    1、前言 网上找了很多redis分布式限流方案,要不就是太大,需要引入第三方jar,而且还无法正常运行,要不就是定时任务定时往key中放入数据,使用的时候调用,严重影响性能,所以着手...
    99+
    2022-11-12
  • SpringBoot整合Redis实现热点数据缓存的示例代码
    我们以IDEA + SpringBoot作为 Java中整合Redis的使用 的测试环境 首先,我们需要导入Redis的maven依赖 <!-- Redis的maven依赖包 ...
    99+
    2023-03-13
    SpringBoot热点数据缓存 SpringBoot整合Redis缓存
  • 利用Python通过商品条形码查询商品信息的实现示例
    目录一 商品条形码二 查询商品条形码的目的三 Python实现3.1 爬取网站介绍3.2 python代码实现提前说明,由于博文重在讲解,代码一体性有一定程度的破坏。如想要省事需要完...
    99+
    2022-11-11
  • SpringBoot实现扫码登录的示例代码
    目录一、首先咱们需要一张表二、角色都有哪些三、接口都需要哪些?四、步骤五、疯狂贴代码Spring Boot中操作WebSocket最近有个项目涉及到websocket实现扫码登录,看...
    99+
    2022-11-13
  • Unity实现菜品识别的示例代码
    接口介绍: 识别超过9千种菜品,支持客户创建属于自己的菜品图库,可准确识别图片中的菜品名称、位置、卡路里信息,并获取百科信息,适用于多种客户识别菜品的业务场景中。 创建应用: &nb...
    99+
    2022-11-13
  • Java实现Redis哨兵的示例代码
    前言: 本文将采用文字+代码的方式,讲解redis版哨兵的实现,所有代码都将写在一个类中,每个属性和方法都会结合文字加以说明。 1. 哨兵(Sentinel)主要功能如下: 1、不时...
    99+
    2022-11-13
  • Redis实现UV统计的示例代码
    目录一、HyperlogLog1、为什么用HyperLogLog2、HyperLogLog是什么二、实现UV统计一、HyperLogLog 1、为什么用HyperLogLog 先介绍两个概念: UV:全称 Unique ...
    99+
    2023-01-29
    Redis UV统计
  • SpringBoot实现api加密的示例代码
    目录SpringBoot的API加密对接项目介绍什么是RSA加密加密实战实战准备真刀真枪解密实战实战准备真刀真枪总结项目坑点SpringBoot的API加密对接 在项目中,为了保证数...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作