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
SpringMVC集成rabbitmq:优化秒杀下单环节的更多相关文章
- 集成RabbitMQ做秒杀
由于秒杀的并发量太大,所以仅仅使用缓存是不够的,还需要用到RabbitMQ. 这里推荐一款用于分库分表的中间件:mycat 解决超卖的问题(看第五章节): 秒杀接口优化: 实操: 然后把下载好的文件上 ...
- SpringMVC集成rabbitMQ
Maven引入相关jar <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-c ...
- SpringBoot集成rabbitmq(一)
前言 Rabbitmq是一个开源的消息代理软件,是AMQP协议的实现.核心作用就是创建消息队列,异步发送和接收消息.通常用来在高并发中处理削峰填谷.延迟处理.解耦系统之间的强耦合.处理秒杀订单. 入 ...
- spring集成rabbitMq(非springboot)
首先 , pom文件需要加入spring集成rabbitMq的依赖: <dependency> <groupId>org.springframework.amqp</gr ...
- rabbitMQ第五篇:Spring集成RabbitMQ
前面几篇讲解了如何使用rabbitMq,这一篇主要讲解spring集成rabbitmq. 首先引入配置文件org.springframework.amqp,如下 <dependency> ...
- EhCache WebCache 与 SpringMVC集成时 CacheManager冲突的问题
转自:点击打开链接 http://www.cnblogs.com/daxin/p/3560989.html EhCache WebCache 与 SpringMVC集成时 CacheManager冲突 ...
- spring+springMVC集成(annotation方式)
spring+springMVC集成(annotation方式) SpringMVC+Spring4.0+Hibernate 简单的整合 MyBatis3整合Spring3.SpringMVC3
- SpringMVC 集成velocity
前言 没有美工的时代自然少不了对应的模板视图开发,jsp时代我们用起来也很爽,物极必反,项目大了,数据模型复杂了jsp则无法胜任. 开发环境 idea2016.jdk1.8.tomcat8.0.35 ...
- RabbitMQ第四篇:Spring集成RabbitMQ
前面几篇讲解了如何使用rabbitMq,这一篇主要讲解spring集成rabbitmq. 首先引入配置文件org.springframework.amqp,如下 <dependency> ...
随机推荐
- vue-These relative modules were not found
今天在做vue2.0+webpack的项目的时候,本来一切正常,整理了一下文件夹分类,就是把一些基础的组件新建了一个文件夹移进去,然后就报了以下的错误,其他东西都没改 最后网上找了很多资料,有说配置文 ...
- 微信小程序之canvas绘制海报分享到朋友圈
绘制canvas内容 首先,需要写一个canvas标签,给canvas-id命名为shareBox <canvas canvas-id="shareBox"></ ...
- 记使用aliyun-log-logback-appender 报错no applicable action for [encoder], current ElementPath is [[configuration][appender][encoder]]
依赖: <dependency> <groupId>com.aliyun.openservices</groupId> <artifactId>aliy ...
- Linux yun命令使用报错:File "/usr/bin/yum", line 30 except KeyboardInterrupt, e:
原文参考:https://www.cnblogs.com/caiji/p/7891923.html 使用yum更新perl源,报错 问题出现原因: yum包管理是使用python2.x写的,将pyth ...
- python中的双向链表实现
引子 双向链表比之单向链表,多数操作方法的实现都没有什么不同,如is_empty, __len__, traverse, search.这些方法都没有涉及节点的变动,也就可通过继承单向链表来实现即可. ...
- django 源码报错
启动django ,一直提示一个 AttributeError: 'str' object has no attribute 'decode' 哥,查了一下午google,就怕是自己判断错了,最后在一 ...
- C# 使用NPOI 处理Excel(Datable与Excel相互转换)
VS上有自带的程序集可以读取,但是总是会出现这样或那样的问题,让人恨得牙疼!而且效率太慢了.用NPOI就好多了,比较快,而且稳定,还简单,引用相应的程序集就好了. Excel转换成Datable pr ...
- 【mysql】must reset your password using ALTER USER statement before executing this statement
问题描述: must reset your password using ALTER USER statement before executing this statement 登录centos服务 ...
- 3-3 Hadoop集群完全分布式配置部署
Hadoop集群完全分布式配置部署 下面的部署步骤,除非说明是在哪个服务器上操作,否则默认为在所有服务器上都要操作.为了方便,使用root用户. 1.准备工作 1.1 centOS6服务器3台 手动指 ...
- 琐事集 vol 2
vol 2-0 宝宝,你是不是该看书咯? 她正瘫在沙发上看剧 我刚提起看书,她惊恐地看了看我 然后眼白一翻,彻底地瘫平了 宝宝? “宝宝睡着了.” 你下周就要考护师了!很难得,她认真地睁开眼,信誓旦旦 ...