• springboot + rabbitmq + redis实现秒杀


    实现说明

    在这里插入图片描述

    • 这里的核心在于如何在大并发的情况下保证数据库能扛得住压力,因为大并发的瓶颈在于数据库。如果用户的请求直接从前端传到数据库,显然,数据库是无法承受几十万上百万甚至上千万的并发量的。因此,我们能做的只能是减少对数据库的访问。例如,前端发出了100万个请求,通过我们的处理,最终只有10个会访问数据库,这样就会大大提升系统性能。再针对秒杀这种场景,因为秒杀商品的数量是有限的,因此采用上述实现方案。
    • 假如,某个商品可秒杀的数量是10,那么在秒杀活动开始之前,把商品的ID和数量加载到Redis缓存。当服务端收到请求时,首先预减Redis中的数量,如果数量减到小于0时,那么随后的访问直接返回秒杀失败的信息。也就是说,最终只有10个请求会去访问数据库。
    • 如果商品数量比较多,比如1万件商品参与秒杀,那么就有1万*10=10万个请求并发去访问数据库,数据库的压力还是会很大。这里就用到了另外一个非常重要的组件:消息队列。我们不是把请求直接去访问数据库,而是先把请求写到消息队列中,做一个缓存,然后再去慢慢的更新数据库。这样做之后,前端用户的请求可能不会立即得到响应是成功还是失败,很可能得到的是一个排队中的返回值,这个时候,需要客户端去服务端轮询,因为我们不能保证一定就秒杀成功了。当服务端出队,生成订单以后,把用户ID和商品ID写到缓存中,来应对客户端的轮询就可以了。这样处理以后,我们的应用是可以很简单的进行分布式横向扩展的,以应对更大的并发。当然,秒杀系统还有很多要处理的事情,比如限流防刷、分布式Session等等。

    1、工具准备
    rabbitmq安装:https://blog.csdn.net/m0_67392811/article/details/123827105
    界面地址:http://localhost:15672/#/
    用户名 guest
    密码 guest
    在这里插入图片描述

    redis安装:https://blog.csdn.net/chen15369337607/article/details/119334531
    jmeter安装:https://blog.csdn.net/yy1209357299/article/details/125480058
    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;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    订单表

    -- ----------------------------
    -- 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;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    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);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    实现层

    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();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    下订单

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    11、测试
    新建线程组——新建取样器(http请求)——新建查看结果树
    在这里插入图片描述
    12、测试结果
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    C++ Reference: Standard C++ Library reference: C Library: cwctype: iswlower
    JVM垃圾回收机制
    【学生管理系统】权限管理
    基于SSM的旅游景点购票管理系统
    抖音小店:庞大用户基数与强大商业化能力的未来发展
    手把手带你学习Scala(三)-Scala运算符和流程控制
    Java abstract 关键字(keyword)
    最快的包管理器--pnpm创建vue项目完整步骤
    推广微海报制作教程,轻松搞定
    【C++类和对象】const成员函数及流插入提取
  • 原文地址:https://blog.csdn.net/yy1209357299/article/details/125523406