springboot +rabbitmq+redis实现秒杀示例

目录
  • 实现说明
  • 1、工具准备
  • 2、数据表
  • 3、pom
  • 4、代码结构
  • 5、配置config
  • 6、订单业务层
  • 7、redis实现层
  • 8、mq实现层
  • 9、redis模拟初始化库存量
  • 10、controller控制层
  • 11、测试
  • 12、测试结果

实现说明

这里的核心在于如何在大并发的情况下保证数据库能扛得住压力,因为大并发的瓶颈在于数据库。如果用户的请求直接从前端传到数据库,显然,数据库是无法承受几十万上百万甚至上千万的并发量的。因此,我们能做的只能是减少对数据库的访问。例如,前端发出了100万个请求,通过我们的处理,最终只有10个会访问数据库,这样就会大大提升系统性能。再针对秒杀这种场景,因为秒杀商品的数量是有限的,因此采用上述实现方案。

假如,某个商品可秒杀的数量是10,那么在秒杀活动开始之前,把商品的ID和数量加载到Redis缓存。当服务端收到请求时,首先预减Redis中的数量,如果数量减到小于0时,那么随后的访问直接返回秒杀失败的信息。也就是说,最终只有10个请求会去访问数据库。

如果商品数量比较多,比如1万件商品参与秒杀,那么就有1万*10=10万个请求并发去访问数据库,数据库的压力还是会很大。这里就用到了另外一个非常重要的组件:消息队列。我们不是把请求直接去访问数据库,而是先把请求写到消息队列中,做一个缓存,然后再去慢慢的更新数据库。这样做之后,前端用户的请求可能不会立即得到响应是成功还是失败,很可能得到的是一个排队中的返回值,这个时候,需要客户端去服务端轮询,因为我们不能保证一定就秒杀成功了。当服务端出队,生成订单以后,把用户ID和商品ID写到缓存中,来应对客户端的轮询就可以了。这样处理以后,我们的应用是可以很简单的进行分布式横向扩展的,以应对更大的并发。当然,秒杀系统还有很多要处理的事情,比如限流防刷、分布式Session等等。

1、工具准备

rabbitmq安装:https://www.jb51.net/article/253706.htm

界面地址:http://localhost:15672/#/
用户名 guest
密码 guest

redis安装:https://www.jb51.net/article/145704.htm
jmeter安装:https://www.jb51.net/article/232152.htm

2、数据表

商品表

-- ----------------------------
-- Table structure for stock
-- ----------------------------
DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
  `count` int(11) NOT NULL COMMENT '库存',
  `sale` int(11) NOT NULL COMMENT '已售',
  `version` int(11) NOT NULL COMMENT '乐观锁,版本号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

订单表

-- ----------------------------
-- Table structure for stock_order
-- ----------------------------
DROP TABLE IF EXISTS `stock_order`;
CREATE TABLE `stock_order` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `sid` int(11) NOT NULL COMMENT '库存ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3、pom

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-amqp</artifactId>
 </dependency>

 <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

4、代码结构

5、配置config

mq配置

package com.yy.msserver.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author code
 * @Date 2022/6/27 14:03
 * Description rabbitmq config
 * Version 1.0
 */
@Configuration
public class MyRabbitMQConfig {

    //库存交换机
    public static final String STORY_EXCHANGE = "STORY_EXCHANGE";

    //订单交换机
    public static final String ORDER_EXCHANGE = "ORDER_EXCHANGE";

    //库存队列
    public static final String STORY_QUEUE = "STORY_QUEUE";

    //订单队列
    public static final String ORDER_QUEUE = "ORDER_QUEUE";

    //库存路由键
    public static final String STORY_ROUTING_KEY = "STORY_ROUTING_KEY";

    //订单路由键
    public static final String ORDER_ROUTING_KEY = "ORDER_ROUTING_KEY";
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    //创建库存交换机
    @Bean
    public Exchange getStoryExchange() {
        return ExchangeBuilder.directExchange(STORY_EXCHANGE).durable(true).build();
    }
    //创建库存队列
    @Bean
    public Queue getStoryQueue() {
        return new Queue(STORY_QUEUE);
    }
    //库存交换机和库存队列绑定
    @Bean
    public Binding bindStory() {
        return BindingBuilder.bind(getStoryQueue()).to(getStoryExchange()).with(STORY_ROUTING_KEY).noargs();
    }
    //创建订单队列
    @Bean
    public Queue getOrderQueue() {
        return new Queue(ORDER_QUEUE);
    }
    //创建订单交换机
    @Bean
    public Exchange getOrderExchange() {
        return ExchangeBuilder.directExchange(ORDER_EXCHANGE).durable(true).build();
    }
    //订单队列与订单交换机进行绑定
    @Bean
    public Binding bindOrder() {
        return BindingBuilder.bind(getOrderQueue()).to(getOrderExchange()).with(ORDER_ROUTING_KEY).noargs();
    }
}

redis配置

package com.yy.msserver.config;

/**
 * @author code
 * @Date 2022/6/27 14:06
 * Description redis config
 * Version 1.0
 */
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
 * @author code
 *@Date 2022/6/27 14:05
 *Description redis config
 *Version 1.0
 */
@Configuration
public class RedisConfig {
    // 配置redis得配置详解
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

6、订单业务层

接口层

package com.yy.msserver.service;

import com.yy.msserver.model.vo.Stock;

/**
 * @author code
 * @Date 2022/6/24 9:25
 * Description 订单接口
 * Version 1.0
 */
public interface StockOrderService {
    public Integer createOrder(Integer id);

    public void decrByStock(Integer id);
}

实现层

package com.yy.msserver.service.impl;

import com.yy.msserver.dao.StockMapper;
import com.yy.msserver.dao.StockOrderMapper;
import com.yy.msserver.model.vo.Stock;
import com.yy.msserver.model.vo.StockOrder;
import com.yy.msserver.service.StockOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * @author code
 * @Date 2022/6/24 9:25
 * Description 订单实现
 * Version 1.0
 */
@Service
public class StockOrderServiceImpl implements StockOrderService {
    @Autowired
    private StockOrderMapper stockOrderMapper;

    @Autowired
    private StockMapper stockMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer createOrder(Integer id) {
        //校验库存
        Stock stock = checkStock(id);
//        if(stock.getCount()>0){
//            System.out.println("当前库存:" + stock.getCount());
//            //扣库存
//            if(updateSale(stock) == 1){
//
//            }else {
//                return 0;
//            }
//        }
//        return 0;
        return createOrder(stock);
    }

    @Override
    public void decrByStock(Integer id){
        //校验库存
        Stock stock = checkStock(id);
        if(stock.getCount()>0){
            System.out.println("当前库存:" + stock.getCount());
            //扣库存
            updateSale(stock);
        }
    }

    //校验库存
    private Stock checkStock(Integer id) {
        return stockMapper.checkStock(id);
    }

    //扣库存
    private int updateSale(Stock stock){
        return stockMapper.updateSale(stock);
    }

    //下订单
    private Integer createOrder(Stock stock){
        StockOrder order = new StockOrder();
        order.setSid(stock.getId());
        order.setCreateTime(new Date());
        order.setName(stock.getName());
        stockOrderMapper.createOrder(order);
        return order.getId();
    }
}

7、redis实现层

package com.yy.msserver.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author code
 * @Date 2022/6/27 17:25
 * Description redis
 * Version 1.0
 */
@Service
public class RedisService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 设置String键值对
     * @param key
     * @param value
     * @param millis
     */
    public void put(String key, Object value, long millis) {
        redisTemplate.opsForValue().set(key, value, millis, TimeUnit.MINUTES);
    }
    public void putForHash(String objectKey, String hkey, String value) {
        redisTemplate.opsForHash().put(objectKey, hkey, value);
    }
    public <T> T get(String key, Class<T> type) {
        return (T) redisTemplate.boundValueOps(key).get();
    }
    public void remove(String key) {
        redisTemplate.delete(key);
    }
    public boolean expire(String key, long millis) {
        return redisTemplate.expire(key, millis, TimeUnit.MILLISECONDS);
    }
    public boolean persist(String key) {
        return redisTemplate.hasKey(key);
    }
    public String getString(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }
    public Integer getInteger(String key) {
        return (Integer) redisTemplate.opsForValue().get(key);
    }
    public Long getLong(String key) {
        return (Long) redisTemplate.opsForValue().get(key);
    }
    public Date getDate(String key) {
        return (Date) redisTemplate.opsForValue().get(key);
    }

    /**
     * 对指定key的键值减一
     * @param key
     * @return
     */
    public Long decrBy(String key) {
        return redisTemplate.opsForValue().decrement(key);
    }
}

8、mq实现层

减库存

package com.yy.msserver.service;

import com.yy.msserver.config.MyRabbitMQConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author code
 * @Date 2022/6/27 15:22
 * Description mq商品信息
 * Version 1.0
 */
@Slf4j
@Service
public class MQStockService {
    @Autowired
    private StockOrderService stockService;
    /**
     * 监听库存消息队列,并消费
     * @param id
     */
    @RabbitListener(queues = MyRabbitMQConfig.STORY_QUEUE)
    public void decrByStock(Integer id) {
        /**
         * 调用数据库service给数据库对应商品库存减一
         */
        log.info("减库存");
        stockService.decrByStock(id);
    }
}

下订单

package com.yy.msserver.service;

import com.yy.msserver.config.MyRabbitMQConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author code
 * @Date 2022/6/27 15:19
 * Description mq 订单队列
 * Version 1.0
 */
@Service
@Slf4j
public class MQOrderService {
    @Autowired
    private StockOrderService orderService;
    /**
     * 监听订单消息队列,并消费
     *
     * @param id
     */
    @RabbitListener(queues = MyRabbitMQConfig.ORDER_QUEUE)
    public void createOrder(Integer id) {
        log.info("收到订单消息");
        orderService.createOrder(id);
    }
}

9、redis模拟初始化库存量

package com.yy.msserver.config;

import com.yy.msserver.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author code
 * @Date 2022/6/29 14:08
 * Description 初始化
 * Version 1.0
 */
@Component
public class InitConfig {
    @Autowired
    private RedisService redisService;
    /**
     * redis初始化商品的库存量和信息
     * @param
     * @throws Exception
     */
    @PostConstruct
    public void init() {
        redisService.put("1", 10, 20);
    }
}

10、controller控制层

 /**
     * 使用redis+消息队列进行秒杀实现
     *
     * @param id 商品id
     * @return
     */
    @GetMapping( value = "/sec",produces = "application/json;charset=utf-8")
    @ResponseBody
    public String sec(@RequestParam(value = "id") int id) {

        String message = null;
        //调用redis给相应商品库存量减一
        Long decrByResult = redisService.decrBy(id+"");
        if (decrByResult >= 0) {
            /**
             * 说明该商品的库存量有剩余,可以进行下订单操作
             */
            //发消息给库存消息队列,将库存数据减一
            rabbitTemplate.convertAndSend(MyRabbitMQConfig.STORY_EXCHANGE, MyRabbitMQConfig.STORY_ROUTING_KEY, id);

            //发消息给订单消息队列,创建订单
            rabbitTemplate.convertAndSend(MyRabbitMQConfig.ORDER_EXCHANGE, MyRabbitMQConfig.ORDER_ROUTING_KEY, id);
            message = "商品" + id + "秒杀成功";
        } else {
            /**
             * 说明该商品的库存量没有剩余,直接返回秒杀失败的消息给用户
             */
            message ="商品" + id + "秒杀商品的库存量没有剩余,秒杀结束";
        }
        return message;
    }

11、测试

新建线程组——新建取样器(http请求)——新建查看结果树

12、测试结果

到此这篇关于springboot +rabbitmq+redis实现秒杀的文章就介绍到这了,更多相关springboot rabbitmq redis秒杀内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

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

    目录 业务分析 创建表 功能实现 1.用户校验 2.下单 3.减少库存 4.支付 总结 业务分析 一般而言,商品秒杀大概可以拆分成以下几步: 用户校验 校验是否多次抢单,保证每个商品每个用户只能秒杀一次 下单 订单信息进入消息队列,等待消费 减少库存 消费订单消息,减少商品库存,增加订单记录 付款 十五分钟内完成支付,修改支付状态 创建表 goods_info 商品库存表 列 说明 id 主键(uuid) goods_name 商品名称 goods_stock 商品库存 package com.

  • springboot +rabbitmq+redis实现秒杀示例

    目录 实现说明 1.工具准备 2.数据表 3.pom 4.代码结构 5.配置config 6.订单业务层 7.redis实现层 8.mq实现层 9.redis模拟初始化库存量 10.controller控制层 11.测试 12.测试结果 实现说明 这里的核心在于如何在大并发的情况下保证数据库能扛得住压力,因为大并发的瓶颈在于数据库.如果用户的请求直接从前端传到数据库,显然,数据库是无法承受几十万上百万甚至上千万的并发量的.因此,我们能做的只能是减少对数据库的访问.例如,前端发出了100万个请求,

  • SpringBoot整合Redis管道的示例代码

    目录 1. Redis 之管道(pipeline) 2. SpringBoot 整合 Redis 管道实例 1. Redis 之管道(pipeline) 执行一个Redis命令,Redis客户端和Redis服务器就需要执行以下步骤: 客户端发送命令到服务器: 服务器接受命令请求,执行命令,产生相应的结果: 服务器返回结果给客户端: 客户端接受命令的执行结果,并向用户展示. Redis命令所消耗的大部分时间都用在了发送命令请求和接收命令结果上面,把任意多条Redis命令请求打包在一起,然后一次性地

  • SpringBoot集成Redis的实现示例

    前面一篇文章已经写了如何搭建一个单机版Redis服务, 那么我们应该怎么在现有的系统中集成进来呢? 由于笔者使用的编程语言是Java, 所以本篇文章主要描述SpringBoot如何集成单Redis节点完成数据的增删改查. SpringBoot环境 快速搭建一个SpringBoot工程 进入 https://start.spring.io 网站, 使用该网站初始化一个SpringBoot工程 添加相关依赖 因为使用spring initializer已经帮我们把Redis的依赖建立好了; 但是由于

  • SpringBoot整合Redis实现消息发布与订阅的示例代码

    当我们在多个集群应用中使用到本地缓存时,在数据库数据得到更新后,为保持各个副本当前被修改的数据与数据库数据保持同步,在数据被操作后向其他集群应用发出被更新数据的通知,使其删除;下次当其他应用请求该被更新的数据时,应用会到数据库去取,也就是最新的数据,从而使得被更新数据与数据库保持同步! 能实现发送与接收信息的中间介有很多,比如:RocketMQ.RabbitMQ.ActiveMQ.Kafka等,本次主要简单介绍Redis的推送与订阅功能并集成Spring Boot的实现. 1.添加SpringB

  • springboot集成redis实现简单秒杀系统

    本文实例为大家分享了springboot集成redis实现简单秒杀系统的具体代码,供大家参考,具体内容如下 项目是有地址的,我会放到文章的最后面 1. 直接service,我们会介绍两种秒杀模式 public interface GoodsService { /** * 通过lua脚本实现的秒杀 * @param skuCode 商品编码 * @param buyNum 购买数量 * @return 购买数量 */ Long flashSellByLuaScript(String skuCode

  • SpringBoot结合Redis哨兵模式的实现示例

    Redis哨兵模式 Redis Sentinel介绍 Redis Sentinel是Redis高可用的实现方案.Sentinel是一个管理多个Redis实例的工具,它可以实现对Redis的监控.通知.自动故障转移. Redis Sentinel主要功能 Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务: 监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常. 提醒(Notificatio

  • SpringBoot整合Redis正确的实现分布式锁的示例代码

    前言 最近在做分块上传的业务,使用到了Redis来维护上传过程中的分块编号. 每上传完成一个分块就获取一下文件的分块集合,加入新上传的编号,手动接口测试下是没有问题的,前端通过并发上传调用就出现问题了,并发的get再set,就会存在覆盖写现象,导致最后的分块数据不对,不能触发分块合并请求. 遇到并发二话不说先上锁,针对执行代码块加了一个JVM锁之后问题就解决了. 仔细一想还是不太对,项目是分布式部署的,做了负载均衡,一个节点的代码被锁住了,请求轮询到其他节点还是可以进行覆盖写,并没有解决到问题啊

  • SpringBoot+RabbitMQ方式收发消息的实现示例

    本篇会和SpringBoot做整合,采用自动配置的方式进行开发,我们只需要声明RabbitMQ地址就可以了,关于各种创建连接关闭连接的事都由Spring帮我们了~ 交给Spring帮我们管理连接可以让我们专注于业务逻辑,就像声明式事务一样易用,方便又高效. 祝有好收获,先赞后看,快乐无限. 本文代码:   https://gitee.com/he-erduo/spring-boot-learning-demo https://github.com/he-erduo/spring-boot-lea

  • SpringBoot整合Redis的示例

    redis是最常用的缓存数据库,常用于存储用户登录token.临时数据.定时相关数据等. redis是单线程的,所以redis的操作是原子性的,这样可以保证不会出现并发问题. redis基于内存,速度非常快,据测试,redis读的速度是110000次/s,写的速度是81000次/s 本节介绍SpringBoot引入redis,以及使用RedisTemplate来操作redis数据. 采用SpringBoot 2.1.9.RELEASE,对应示例代码在:https://github.com/lao

随机推荐