商品秒杀的基本思想,借助hutool工具包实现

pom文件需要多引入的jar包

        <!--手动引入的hutool工具包的依赖(需要使用bloom过滤器)-->
		<!--这个第三方工具包的作用是用来判别有的人秒杀后重复秒杀-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.9</version>
        </dependency>

        <!--手动引入fastjson的依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>

步骤:

  1. 在消息生产者的配置类中声明一个BitMapBloomFilter的bean对象,该对象用来过滤是否由重复的用户进入秒杀

    @Bean
        public BitMapBloomFilter bitMapBloomFilter(){
            //构造器参数为位图大小(可以理解为过滤面的大小)
            return new BitMapBloomFilter(100);
        }
    
  2. 在消息消费者的类中声明队列+监听器

    //配置队列
        @Bean
        public Queue SpikeQueue(){
            return new Queue("boot.spike.queue");
        }
    
    	//消息监听器
    	//@RabbitListener的concurrency属性用来设置多个线程消费该队列
    	@RabbitListener(queues = "boot.spike.queue", concurrency = "3-5")
        public void handlerSpikeMsg(Message message, Channel channel){
    
            //从队列中获取消息 --- json串 --- 拿userId和goodsId
            String jsonStr = new String(message.getBody());
            JSONObject jsonObj = JSON.parseObject(jsonStr);
            Integer userId = jsonObj.getInteger("userId");
            Integer goodsId = jsonObj.getInteger("goodsId");
    
            try {
                //执行业务
                //spikeService.spikeService(userId, goodsId);
                //签收消息
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            } catch (IOException e) {
                throw new RuntimeException("秒杀失败....");
            }
        }
    
  3. 当controller层的方法接收到秒杀请求后,使用注入的BitMapBloomFilter对象来判断。

    	//注入bloom过滤器
        @Autowired
        private BitMapBloomFilter bitMapBloomFilter;
    
        //注入rabbitmq模板
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        //注入redis模板
        @Autowired
        private StringRedisTemplate redisTemplate;
    
    
    	/*
          处理秒杀请求的方法,请求url为/doSpike,并传参用户id userId、商品id goodsId,
          向客户端响应字符串文本;
         */
        @RequestMapping("/doSpike")
        public String doSpike(Integer userId, Integer goodsId){
            /*
              1.通过bloom过滤器判断用户是否参与过该商品的秒杀:
                一个用户对一个商品只能秒杀一次
             */
            //通过用户id和商品id生成唯一标识
            String spikeId = userId+"-"+goodsId;
            //判断bloom过滤器的contains()方法判断是否存在该标识,存在则返回true表示已参与秒杀,不存在则返回false表示未参与秒杀
            if(bitMapBloomFilter.contains(spikeId)){
                return "您已参加过该商品的抢购...";
            }
            
            /*
              2.操作redis判断是否还有库存,如果还有则对库存做扣减
             */
            //商品库存在redis中以键goods_stock:商品id,值库存数存储;
            //对键goods_stock:商品id的值即库存数做递减,并返回递减后的库存数;
            Long stock = redisTemplate.opsForValue().decrement("goods_stock:" + goodsId);
            if(stock<0){
                return "商品已被抢完了,下次早点来哦...";
            }
            
            //到此说明秒杀成功了
            //3.将用户id和商品id生产的唯一标识添加到bloom过滤器
            bitMapBloomFilter.add(spikeId);
            
            /*
              4.将订单信息发送到rabbitmq,即将用户id和商品id发送到rabbitmq
             */
            //将用户id和商品id存到map再转成json字符串发送给rabbitmq
            Map<String, Integer> map = new HashMap<>();
            map.put("userId", userId);
            map.put("goodsId", goodsId);
            //使用JSON来处理需要传递的对象,将对象转换为字符串后传入消息队列
            String jsonStr = JSON.toJSONString(map);
            //发送消息到名称为spike.queue的队列
            rabbitTemplate.convertAndSend("spike.queue", jsonStr);
            
            //5.向客户端返回成功结果
            return "正在拼命抢购中,请稍后去订单查看...";
        }
    

    这样,在页面发送秒杀url请求后,会来到controller层的方法中,再经过布隆过滤器判断传进来的用户是否已经参与过秒杀,如果参与过则退出方法,如果没有则将用户信息和商品信息传入消息队列中;再经过消费服务项目中的监听器监听对应的消息队列,对该队列的消息进行判断和业务处理

  4. 关于如何在项目启动时候,自动查询数据库,将数据库查询到的需要秒杀的商品注入redis中,这里采用了实现CommandLineRunner接口的方式

    //实现CommandLineRunner接口并重写run(),run()方法会在boot应用启动时执行;
    @Component
    public class MysqlToRedis implements CommandLineRunner {
    
        //注入redis模板
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        //注入GoodsMapper
        //商品mapper接口
        @Autowired
        private GoodsMapper goodsMapper;
    
        //boot应用启动将mysql数据同步到redis
        @Override
        public void run(String... args) throws Exception {
    
            //查询所有参数秒杀的商品
            List<Goods> spikeGoods = goodsMapper.getSpikeGoods();
    
            //将每个参与秒杀的商品,以goods_stock:商品id为键,库存量为值,存储到redis
            if(!CollectionUtils.isEmpty(spikeGoods)){
                for (Goods goods : spikeGoods) {
                    Integer id = goods.getGoodsid();
                    Integer stock = goods.getStock();
                    //key为"goods_stock:"+被秒杀商品的id":
                    //value为被秒杀商品的余额
                    redisTemplate.opsForValue().set("goods_stock:"+id, stock.toString());
                }
            }
    
        }
    }
    
  5. 秒杀重点:已经参与过秒杀的用户不能再进行秒杀,同时每个请求到达方法后都需要获得秒杀的商品的数量,被秒杀的商品的数量不能比0少;这里判断商品数量是使用的redis判断被秒杀的商品数量,每次业务处理前会先对redis中的商品的数量减一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值