SpringMVC集成rabbitmq:优化秒杀下单环节

前言

        上一篇在springboot中基于自动配置集成了rabbitmq。那么回到最初的话题中就是想在秒杀下单环节增加排队机制,从而达到限流的目的。

 

优化秒杀下单流程

      之前是在控制器里拿到客户端请求后直接入库、减库存。如果碰到羊毛党其实这套机制是不行的。并发量高的时候,库存数量也会不准确。那么引入rabbitmq则在下单时让用户信息产生一条消息入队。然后消费者处理下单(是否重复下单、下单失败、库存不够)。客户端接受到请求已入队列(response引入state处理交互)后发起ajax轮询请求,处理成功则跳转下单成功页或者结束本次交互。

 

1、下单(秒杀接口)

@RequestMapping(value="/{seckillId}/{md5}/execute",method = RequestMethod.POST,produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public SeckillResult execute(@PathVariable("seckillId")Long seckillId,
                                                   @PathVariable("md5")String md5,
                                                   @CookieValue(value="phone",required=false)Long phone){

        if(phone==null){
            return new SeckillResult(false,"手机号未注册");
        }

        SeckillResult result=null;

        try{

            SeckillExecution execution=seckillService.executeSeckill(seckillId,phone,md5);
            result=new SeckillResult(true,execution);

        }catch(RepeatKillException e){

            SeckillExecution execution=new SeckillExecution(seckillId,-1,"重复秒杀");
            result=new SeckillResult(true,execution);


        }catch(SeckillCloseException e){

            SeckillExecution execution=new SeckillExecution(seckillId,0,"秒杀结束");
            result=new SeckillResult(true,execution);

        }catch (Exception e){

            SeckillExecution execution=new SeckillExecution(seckillId,-2,"系统异常");
            result=new SeckillResult(true,execution);

        }

        return result;

    }

2、下单业务方法(Service) 这里就要引入排队

 @Override
    public SeckillExecution executeSeckill(long seckillId, long phone, String md5)
            throws SeckillException,RepeatKillException,SeckillCloseException {

        if (md5 == null || !md5.equals(getMd5(seckillId))) {
            throw new SeckillException("非法请求");
        }

        Date now = new Date();

        try {
            int insertCount = successKillDao.insertSuccessKilled(seckillId, phone);
            if (insertCount <= 0) {
                throw new RepeatKillException("重复秒杀");

            } else {

                //请求入队
                MiaoshaUser miaoshaUser=new MiaoshaUser();
                miaoshaUser.setPhone(phone);

                MiaoshaMessage miaoshaMessage=new MiaoshaMessage();
                miaoshaMessage.setSeckillId(seckillId);
                miaoshaMessage.setMiaoshaUser(miaoshaUser);

                String miaosha=JSON.toJSONString(miaoshaMessage);
                amqpTemplate.convertAndSend(miaosha);

                return new SeckillExecution(seckillId,0,"请求入队");

                /***
                 * 直接入库操作
                int updateCount = seckillDao.reduceNumber(seckillId, now);
                if (updateCount <= 0) {
                    throw new SeckillCloseException("秒杀已关闭");
                } else {
                    //秒杀成功,可以把秒杀详情和商品详情实体返回
                    SuccessKilled successKilled = successKillDao.queryByIdWithSeckill(seckillId, phone);
                    return new SeckillExecution(seckillId, 1, "秒杀成功", successKilled);
                }
                 ***/
            }

        } catch (SeckillCloseException e) {
            throw e;
        } catch (RepeatKillException e1) {
            throw e1;
        } catch (SeckillException e2) {
            logger.error(e2.getMessage(), e2);
            throw new SeckillException("Unkonwn error:" + e2.getMessage());
        }

    }

3、下单结果接口

@RequestMapping(value="/{seckillId}/{md5}/result",method = RequestMethod.GET,produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public SeckillResult result(@PathVariable("seckillId")Long seckillId,
                                                  @PathVariable("md5")String md5,
                                                  @CookieValue(value="phone",required=false)Long phone){

        SuccessKilled successKilled = seckillService.queryByIdWithSeckill(seckillId, phone);
        SeckillExecution execution=null;
        if(successKilled.getSeckillId()>0){
            execution=new SeckillExecution(seckillId, 1, "下单成功", successKilled);
        }else{
            execution=new SeckillExecution(seckillId, -2, "下单失败", successKilled);
        }

        return new SeckillResult(true,execution);
    }

4、消费者(下单处理)

/**
 * 秒杀请求消费
 **/
public class AmqpConsumer implements MessageListener {

    @Autowired
    SeckillDao seckillDao;

    @Autowired
    SuccessKillDao successKillDao;


    @Override
    public void onMessage(Message message) {
        Date now = new Date();

        MiaoshaMessage miaosha = JSON.parseObject(message.getBody(), MiaoshaMessage.class);

        Long seckillId = miaosha.getSeckillId();
        int updateCount = seckillDao.reduceNumber(seckillId, now);
        if (updateCount <= 0) {
            System.out.println("秒杀下单失败");
        } else {
            System.out.println("秒杀下单成功");

        }


    }
}

5、springmvc集成消息队列配置文件




    
    

    
    
    

    
    
        
            
        
    

    
    

    
    
    
        
    

6、客户端秒杀下单、等待下单结果

 /**秒杀结果**/
    miaosha:function(seckillId,md5,node){
        $.get('/seckill/'+seckillId+'/'+md5+'/result',{},function(result){
            if(result && result["success"]){
                var oData=result["data"];
                if(oData["state"]===1){
                    node.html("下单成功");

                    clearInterval(miaoshaTimer);
                }else{
                    console.log("还在排队种...");
                }
            }
        })
    },
    /**执行秒杀**/
    seckill:function(seckillId,node){

        //获取秒杀地址、控制node节点显示,执行秒杀
        node.hide().html("")

        $.get('/seckill/'+seckillId+'/exposer',{},function(result){

            if(result && result["success"]){
                //在回调函数中执行秒杀操作
                var exposer=result["data"];
                if(exposer["exposed"]){
                    //秒杀已开始
                    var md5=exposer["md5"];
                    var killUrl='/seckill/'+seckillId+'/'+md5+'/execute';
                    console.log(killUrl);

                    $("#killBtn").one('click',function(){
                        //1、禁用秒杀按钮
                        $(this).addClass('disabled');
                        //2、执行秒杀操作
                        $.post(killUrl,{},function(result){
                            if(result && result["success"]){
                                var killResult=result["data"];
                                var state=killResult["state"];
                                var stateInfo=killResult["stateInfo"];

                                node.html(""+stateInfo+"");
                                if(state===0){
                                    //已入队,客户端开始轮询
                                    miaoshaTimer=setInterval(function(){
                                       seckill.miaosha(seckillId,md5,node);
                                    },3000);
                                }
                            }
                        })

                    });

                    node.show();
                }else{
                    //秒杀未开始, 防止浏览器和服务器出现时间差,再次执行倒数计时
                    var now = exposer['now'];
                    var start = exposer['start'];
                    var end = exposer['end'];
                    seckill.countdown(seckillId, now, start, end);
                }

            }else{
                console.log('result:'+result); //没有拿到秒杀地址
            }

        })

    }

 好了,贴了这么多代码,没有示意图怎么能行?

SpringMVC集成rabbitmq:优化秒杀下单环节_第1张图片

SpringMVC集成rabbitmq:优化秒杀下单环节_第2张图片

SpringMVC集成rabbitmq:优化秒杀下单环节_第3张图片

 

总结

      秒杀下单增加排队机制来说对于完整的秒杀系统来说只是其中很少的一部分,这里也只是学习rabbitmq的一个过程。对于秒杀系统来说流量主要是查询多下单少。还需要引入redis,把库存量、商品信息能在秒杀开始前预处理。

 

参考资料

     https://blog.csdn.net/sunweiguo1/article/details/80470792

 

你可能感兴趣的:(SpringMVC集成rabbitmq:优化秒杀下单环节)