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<SeckillExecution> execute(@PathVariable("seckillId")Long seckillId,
                                                   @PathVariable("md5")String md5,
                                                   @CookieValue(value="phone",required=false)Long phone){

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

        SeckillResult<SeckillExecution> result=null;

        try{

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

        }catch(RepeatKillException e){

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

        }catch(SeckillCloseException e){

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

        }catch (Exception e){

            SeckillExecution execution=new SeckillExecution(seckillId,-2,"系统异常");
            result=new SeckillResult<SeckillExecution>(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<SeckillExecution> 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<SeckillExecution>(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集成消息队列配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">

    <!--spring集成rabbitmq-->
    <rabbit:connection-factory id="connectionFactory"
                               host="192.168.80.128"
                               port="5672"
                               username="admin"
                               password="admin"
                               channel-cache-size="5"
                               virtual-host="/" />

    <rabbit:admin connection-factory="connectionFactory"/>
    <!--声明队列-->
    <rabbit:queue durable="true" auto-delete="false" exclusive="false" name="miaosha.queue"/>

    <!--交换器和队列绑定-->
    <rabbit:direct-exchange name="miaosha.exchange">
        <rabbit:bindings>
            <rabbit:binding queue="miaosha.queue" key="miaosha.tag.key"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!--spring rabbitmqTemplate声明-->
    <rabbit:template id="rabbitTemplate"
                     exchange="miaosha.exchange"
                     routing-key="miaosha.tag.key"
                     connection-factory="connectionFactory" />

    <!--消息监听-->
    <bean id="miaoshaConsumer" class="com.seckill.mq.AmqpConsumer"/>
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto">
        <rabbit:listener ref="miaoshaConsumer" queues="miaosha.queue"/>
    </rabbit:listener-container>
</beans>

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("<span class=‘label label-success‘>下单成功</span>");

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

        //获取秒杀地址、控制node节点显示,执行秒杀
        node.hide().html("<button id=‘killBtn‘ class=‘btn btn-primary btn-lg‘>开始秒杀</button>")

        $.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("<span class=‘label label-success‘>"+stateInfo+"</span>");
                                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); //没有拿到秒杀地址
            }

        })

    }

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

总结

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

参考资料

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

原文地址:https://www.cnblogs.com/sword-successful/p/10368820.html

时间: 2024-10-28 10:10:41

SpringMVC集成rabbitmq:优化秒杀下单环节的相关文章

SpringMVC集成rabbitMQ

Maven引入相关jar <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>3.2.4</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.

SpringBoot集成rabbitmq(一)

前言 Rabbitmq是一个开源的消息代理软件,是AMQP协议的实现.核心作用就是创建消息队列,异步发送和接收消息.通常用来在高并发中处理削峰填谷.延迟处理.解耦系统之间的强耦合.处理秒杀订单.  入门rabbitmq之前主要是想了解下秒杀排队订单入库后,异步通知客户端秒杀结果. 基础知识 1.基本概念(角色) 了解rabbitmq之前先要了解3个基本概念:生产者.消费者.代理(队列). rabbitmq在生产者和代理中间做了一层抽象.这样消息生产者和队列就没有直接联系,在中间加入了一层交换器(

rabbitMQ第五篇:Spring集成RabbitMQ

前面几篇讲解了如何使用rabbitMq,这一篇主要讲解spring集成rabbitmq. 首先引入配置文件org.springframework.amqp,如下 <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>1.6.0.RELEASE</version> <

Springmvc集成Shiro实现权限管理

Shiro是一个安全框架,他可以集成其他开发开发框架 如:Springmvc,实现用户身份认证.权限管理等等功能,shiro详细的介绍也就不讲了,这里给出一些关键的知识点吧: 知识点: shiro中默认的过滤器 过滤器名称 过滤器类 描述 anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名过滤器 authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter 如果继续

spring+springMVC集成(annotation方式)

spring+springMVC集成(annotation方式) SpringMVC+Spring4.0+Hibernate 简单的整合 MyBatis3整合Spring3.SpringMVC3

springMVC系列之(三) spring+springMVC集成(annotation方式)

个人认为使用框架并不是很难,关键要理解其思想,这对于我们提高编程水平很有帮助.不过,如果用都不会,谈思想就变成纸上谈兵了!!!先技术,再思想.实践出真知. 1.基本概念 1.1.Spring Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来.它是为了解决企业应用开发的复杂性而创建的.Spring使用

RabbitMQ第四篇:Spring集成RabbitMQ

前面几篇讲解了如何使用rabbitMq,这一篇主要讲解spring集成rabbitmq. 首先引入配置文件org.springframework.amqp,如下 <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>1.6.0.RELEASE</version> <

springBoot(24):集成rabbitmq

注意:springboot支持的amqp规范的中间件只有rabbitmq 第一步:添加依赖 <!-- amqp --> <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-amqp</artifactId> </dependency> 第二步:配置application.yml sp

SpringMVC集成Hessian

SpringMVC集成Hessian 首先强调这里是SpringMVC,不是Spring,这两者在集成Hessian的时候还是有差别的.Spring集成相对简单,网上随便搜一个就行. SpringMVC有点麻烦. 注:如果你还不了解Hessian,可以看Hessian简单示例 前提条件 假设你的SpringMVC环境已经配置了好了. 主要是在web.xml中有了如下的配置: <servlet> <!-- 名字随便,和你springmvc的配置xml一致即可 --> <serv