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> ...
随机推荐
- Oracle (分类、数据库类型、序列)
分类: 1.DDL (定义语句) create .alter .drop 不需要commit create table aaa( tt1 varchart ) 2. DML (操纵语句) lnset ...
- golang二进制bit位的常用操作
golang作为一热门的兼顾性能 效率的热门语言,相信很多人都知道,在编程语言排行榜上一直都是很亮眼,作为一门强类型语言,二进制位的操作肯定是避免不了的,数据的最小的单位也就是位,尤其是网络中封包.拆 ...
- SQL server SELECT 语句的基本结构
SELECT select_list [INTO new_table] [FROM table_source] [WHERE search_condition] [GROUP BY GROUP_BY_ ...
- A_B_Good Bye 2018_cf
A. New Year and the Christmas Ornament time limit per test 1 second memory limit per test 256 megaby ...
- 干货分享:Neutron的PPT,帮助你理解Neutron的各种细节
深入解析Neutron http://files.cnblogs.com/popsuper1982/Neutron.pptx 经典的三节点部署 架构 怎么理解? 更加深入:Tap Interface ...
- JavaScript 异步编程的前世今生(上)
前言 提到 JavaScript 异步编程,很多小伙伴都很迷茫,本人花费大约一周的业余时间来对 JS 异步做一个完整的总结,和各位同学共勉共进步! 目录 part1 基础部分 什么是异步 part2 ...
- Java两种方法实现循环报数
问题描述: 十个猴子围成一圈选大王,依次1-3 循环报数,报到3 的猴子被淘汰,直到最后一只猴子成为大王.问,哪只猴子最后能成为大王? 方法一:Java链表 public class TestAll ...
- Javascript高级编程学习笔记(85)—— Canvas(2)2D上下文
2D上下文 使用2D上下文提供的方法可以绘制简单的2D图形,如矩形,弧线和路径; 2D上下文的坐标开始域<canvas>元素的左上角,原点坐标为(0,0) 后续所有操作的计算都基于原点,x ...
- [Swift]LeetCode1021. 删除最外层的括号 | Remove Outermost Parentheses
A valid parentheses string is either empty (""), "(" + A + ")", or A + ...
- Shell脚本中的for case while循环流程控制语句的使用
shell作为一种脚本编程语言,同样包含循环.分支等其他程序控制结构,从而轻松完成更加复杂.强大的功能. 编写脚本的思路 1 明确脚本的功能 2 编写脚本时会使用到那些命令 ? 3 把变化的 ...