• java初探(1)之秒杀中的rabbitMQ


    rabbitMQ

    消息队列,通过一定的通信协议,生产者和消费者在应用程序内传递通信。

    主要的作用,提高负载,减耦合。

    场景描述:当点击秒杀按钮的那个时刻,有很高的并发量,客户端发出请求之后,会判断库存,如果库存大于0,就判断是否已经下单,如果没有下单,就执行秒杀逻辑,对于秒杀逻辑,分两个步骤,一是减库存,二是创建订单。

    以上就是不使用rabbitMQ的场景描述。

    利用消息队列,我们可以在执行秒杀逻辑之前,将用户和待秒杀的商品进行入队(rabbitMQ的生产者类),然后将秒杀逻辑放在出队上(rabbitMQ的消费者类)

    在客户端页面上创建一个轮询函数,每隔一段时间,设置一个请求是否有秒杀结果,然后进行数据展示,或者跳转。

    通过以上操作,我们可以将用户的请求与返回的结果通过rabbitMQ进行解耦合。从而降低网站的并发。

    • 内存标记库存

      假如后面的操作已经确认库存小于0,则设置一个标记器,直接返回错误信息。

    //内存标记,第一次拦截
            Boolean over = localOverMap.get(goodsId);
            if(over){
                return Result.error(CodeMsg.MIAO_SHA_OVER);
            }
    • 预减库存

      该操作相当于一个拦截器,当我们的秒杀请求进到秒杀控制器中,需要先从redis中减库存,然后判断库存是否充足。因此,需要先在redis中加载商品的库存数

      这里利用控制器继承一个接口,来写一个系统初始化的方法,将商品的库存数放到redis中,每次从秒杀点击过来的请求,先加载该初始化方法,把库存数从数据库里拿出来,(这里可以改进为从缓存中拿值),然后判断库存数,如果库存数小于0,则不需要后续操作,直接返回错误。

    /**
         * 系统初始化
         * @throws Exception
         */
        @Override
        public void afterPropertiesSet() throws Exception {
    
            //得到商品的所有列表
            List<GoodsVo> goodsList = goodsService.listGoodsVo();
            if(goodsList==null){
                return;
            }
    
            for (GoodsVo goods : goodsList) {
                redisService.set(GoodsKey.getMiaoshaGoodsStock,""+goods.getId(),goods.getStockCount());
                //对内存进行标志,若没有结束库存就是false,若有库存就是true
                localOverMap.put(goods.getId(),false);//false表示有库存
            }
    
        }
    
    
    //预减库存
            Long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, "" + goodsId);
            if(stock<0){
                localOverMap.put(goodsId,true);
                return Result.error(CodeMsg.MIAO_SHA_OVER);
            }

    当判断,既有库存,又没有下订单,则执行入队操作,将需要传递的用户和商品名加入到队列中,传递给消费者。

    //判断是否已经秒杀到了
            MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
            if(order!=null){
                return Result.error(CodeMsg.REPEATE_MIAOSHA);
            }
    
            //入队操作
            //创建需要入队的数据类,需要用户类和商品的id。
            MiaoshaMessage message=new MiaoshaMessage();
            message.setGoodsId(goodsId);
            message.setUser(user);
            //调用rabbitMQ生产者的发送方法,发送该数据类
            mqSender.sendMiaoshaMessage(message);
    • 完整MiaoshaController类

    @Controller
    @RequestMapping("/miaosha")
    public class MiaoshaController implements InitializingBean{
    
    
        @Autowired
        private GoodsService goodsService;
    
        @Autowired
        private OrderService orderService;
    
        @Autowired
        private MiaoshaService miaoshaService;
    
        @Autowired
        private RedisService redisService;
    
        @Autowired
        private MQSender mqSender;
    
    
    
        private HashMap<Long,Boolean> localOverMap=new HashMap<>();
    
        /**
         * 系统初始化
         * @throws Exception
         */
        @Override
        public void afterPropertiesSet() throws Exception {
    
            //得到商品的所有列表
            List<GoodsVo> goodsList = goodsService.listGoodsVo();
            if(goodsList==null){
                return;
            }
    
            for (GoodsVo goods : goodsList) {
                redisService.set(GoodsKey.getMiaoshaGoodsStock,""+goods.getId(),goods.getStockCount());
                //对内存进行标志,若没有结束库存就是false,若有库存就是true
                localOverMap.put(goods.getId(),false);//false表示有库存
            }
    
        }
    
        @RequestMapping(value = "/do_miaosha",method = RequestMethod.POST)
        @ResponseBody
        public Result<Integer> list(Model model, MiaoshaUser user,
                           @RequestParam("goodsId") long goodsId){
    
            model.addAttribute("user",user);
            if(user==null){
                //如果没有获取到user值,报异常
                return Result.error(CodeMsg.SESSION_ERROR);
            }
    
    
            //内存标记,第一次拦截
            Boolean over = localOverMap.get(goodsId);
            if(over){
                return Result.error(CodeMsg.MIAO_SHA_OVER);
            }
    
            //预减库存
            Long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, "" + goodsId);
            if(stock<0){
                localOverMap.put(goodsId,true);
                return Result.error(CodeMsg.MIAO_SHA_OVER);
            }
    
            //判断是否已经秒杀到了
            MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
            if(order!=null){
                return Result.error(CodeMsg.REPEATE_MIAOSHA);
            }
    
            //入队操作
            //创建需要入队的数据类,需要用户类和商品的id。
            MiaoshaMessage message=new MiaoshaMessage();
            message.setGoodsId(goodsId);
            message.setUser(user);
            //调用rabbitMQ生产者的发送方法,发送该数据类
            mqSender.sendMiaoshaMessage(message);
    
            return Result.success(0);//返回0代表排队中
    
    
    
    
    
    
    
    
    
    //        //判断库存
    //        GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
    //        Integer stock= goods.getStockCount();
    //
    //        if(stock<=0){
    //            model.addAttribute("errmsg", CodeMsg.MIAO_SHA_OVER.getMsg());
    //            return Result.error(CodeMsg.MIAO_SHA_OVER);
    //        }
    //
    //        //判断是否已经秒杀到了
    //        MiaoshaOrder order=orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
    //
    //        if(order!=null){
    //            return Result.error(CodeMsg.REPEATE_MIAOSHA);
    //        }
    //
    //        //进行秒杀逻辑
    //        //减库存,下订单,写入秒杀订单
    //        OrderInfo orderInfo=miaoshaService.miaosha(user, goods);
    
    //        return Result.success(orderInfo);
        }
    
    
        @RequestMapping(value="/result", method=RequestMethod.GET)
        @ResponseBody
        public Result<Long> miaoshaResult(Model model,MiaoshaUser user,
                                          @RequestParam("goodsId")long goodsId){
            model.addAttribute("user",user);
            if(user==null){
                return Result.error(CodeMsg.SESSION_ERROR);
            }
            //获取秒杀的结果
            long result  =miaoshaService.getMiaoshaResult(user.getId(), goodsId);
    
            return Result.success(result);
        }
    
    
    }
    View Code

    将队列的消息寄出去之后,需要由消费者接收数据,并执行秒杀的具体逻辑

    这里的逻辑代码就很简单,继续判断库存以及是否秒杀到,然后执行秒杀逻辑

    • MQReceiver类
    @Service
    public class MQReceiver {
    
        private static Logger log = LoggerFactory.getLogger(MQReceiver.class);
    
        @Autowired
        private GoodsService goodsService;
    
        @Autowired
        private OrderService orderService;
    
        @Autowired
        private MiaoshaService miaoshaService;
    
        @RabbitListener(queues= MQConfig.MIAOSHA_QUEUE)
        public void receive(String message){
    
            //获取到生产者发送过来的数据
            log.info("收到的消息:"+message);
    
            //转换数据格式
            MiaoshaMessage mm = RedisService.stringToBean(message, MiaoshaMessage.class);
    
            MiaoshaUser user = mm.getUser();
            long goodsId = mm.getGoodsId();
    
            //数据传过来之后,仍然需要判断库存以及是否完成订单
            GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
            int stock = goods.getStockCount();
            if(stock <= 0) {
                return;
            }
            //判断是否已经秒杀到了
            MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
            if(order != null) {
                return;
            }
    
            //当真正秒杀成功之后,再将请求转到秒杀逻辑内
            miaoshaService.miaosha(user,goods);
    
    
        }
    
    }
    View Code

    具体的秒杀逻辑是减库存,然后创建订单,这些代码在之前已经写过。如果没有减库存成功,则表示已经结束秒杀了,在之前的代码中,什么都没做,让事务进行回滚。这次在缓存中设置一个秒杀结束的标志。

    @Transactional
        public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
    
    //        //减库存,下订单,写入秒杀订单
    //        goodsService.reduceStock(goods);
    //        return orderService.createOrder(user, goods);
    
            boolean success = goodsService.reduceStock(goods);
            if (success){
                return orderService.createOrder(user, goods);
            }else {
                setGoodsOver(goods.getId());
                return null;
            }
    
        }
    
        private void setGoodsOver(Long goodsId) {
            redisService.set(MiaoshaKey.isGoodsOver, ""+goodsId, true);
        }
    
        private boolean getGoodsOver(long goodsId) {
            return redisService.exists(MiaoshaKey.isGoodsOver, ""+goodsId);
        }

    做到此处,秒杀的逻辑结束了,但并没有返回任何页面,只是对数据库做了相应的修改。

    接下来应该在客户端进行循环提出请求,来判断是否秒杀成功,在商品的详情页面,当入队成功返回一个0时,就开始启动轮询函数,不停的向服务器发请求,看是否已经下单成功,若成功就跳转到订单详情页面。

    function getMiaoshaResult(goodsId) {
            g_showLoading();//显示处理等待
            //发起ajax请求
            $.ajax({
                url:"/miaosha/result",
                type:"GET",
                data:{
                    goodsId:$("#goodsId").val()
                },
                success:function(data){
                    if (data.code==0){
                        var result=data.data;
                        if(result<0){
                            layer.msg("对不起,秒杀失败");
                        }else if(result==0){
                            //继续轮询
                            setTimeout(function () {
                                getMiaoshaResult(goodsId)
                            },50);
                        }
                        else {
                            layer.confirm("恭喜你,秒杀成功!查看订单?", {btn:["确定","取消"]},
                                function(){
                                    window.location.href="/order_detail.htm?orderId="+result;
                                },
                                function(){
                                    layer.closeAll();
                                });
                        }
                    }else {
                        layer.msg(data.msg);
                    }
    
                },
                error:function(){
                    layer.msg("客户端请求有误");
                }
            });
        }
    View Code

    每次轮询代码请求服务器时,都会将秒杀的结果返回

    public long getMiaoshaResult(Long userId, long goodsId) {
    
            //查看是否下订单成功
            MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(userId, goodsId);
            if (order!=null){
                return order.getOrderId();
            }
            else {
                boolean isover = getGoodsOver(goodsId);
                if(isover){
                    return -1;
                }
                else {
                    return 0;
                }
            }
    
        }

    若订单不为空,则返回订单的id,若为空,则查看有没有库存,若没有库存则返回-1,若有库存,则说明还在排队中,返回0,

    @RequestMapping(value="/result", method=RequestMethod.GET)
        @ResponseBody
        public Result<Long> miaoshaResult(Model model,MiaoshaUser user,
                                          @RequestParam("goodsId")long goodsId){
            model.addAttribute("user",user);
            if(user==null){
                return Result.error(CodeMsg.SESSION_ERROR);
            }
            //获取秒杀的结果
            long result  =miaoshaService.getMiaoshaResult(user.getId(), goodsId);
    
            return Result.success(result);
        }

    到此,rabbitMQ实现的秒杀功能就完成了。

  • 相关阅读:
    python基础一
    IO多路复用
    协程
    线程之互斥锁与递归锁、队列、线程池
    线程
    进程之间的通信与数据共享
    进程
    操作系统的简介
    PyCharm的基本使用指南
    【解决方案】明明安装了库,PyCharm还是提示:No module named bs4 / No module named 'requests'
  • 原文地址:https://www.cnblogs.com/lovejune/p/12348105.html
Copyright © 2020-2023  润新知