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> ...
随机推荐
- linux android开发环境搭建
android开发环境搭建的一些有用链接:1.sdk manager的国内服务器http://www.cnblogs.com/huangjacky/p/4077982.html2.常见问题的解决htt ...
- Jvm 内存模型 —— GC
一.Jvm 原理 二.Jvm 运行时数据区( Run-Time Data Areas ) (主要是关于 non-stack 区域的详细划分) 从上图可以清楚地看到:程序计数器.Jvm 栈.本地方法栈 ...
- 如何实现文件上传 - JavaWeb
直接上代码 ( idea 开发,SpringBoot 框架 ): 首先是Controller的写法: package com.xxx.Controller; import com.xxx.Tools. ...
- Revisiting Network Support for RDMA
重新审视RDMA的网络支持 本文为SIGCOMM 2018会议论文. 笔者翻译了该论文.由于时间仓促,且笔者英文能力有限,错误之处在所难免:欢迎读者批评指正. 本文及翻译版本仅用于学习使用.如果有任何 ...
- WdatePicker 日期控件- 功能及示例
3. 多语言和自定义皮肤多语言支持 通过lang属性,可以为每个日期控件单独配置语言,当然也可以通过WdatePicker.js配置全局的语言语言列表和语言安装说明详见语言配置 示例3-1 多语言 ...
- HttpClient 调用WebAPI时,传参的三种方式
public void Post() { //方法一,传json参数 var d = new { username = " ", password = " ", ...
- [Swift]LeetCode324. 摆动排序 II | Wiggle Sort II
Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3]... ...
- ubuntu配置https
# 重定向 http 到 https server { listen 80; server_name www.domain.com; rewrite ^(.*)$ https://$server_na ...
- MyEclipse设置编码格式
1.设置项目编码格式,右键项目,Properties 选择第一个 2.设置软件编码格式,windows---->Preferences打开"首选项"对话框
- Spring Boot 最核心的 25 个注解,都是干货!
学习和应用 Spring Boot 有一些时间了,你们对 Spring Boot 注解了解有多少呢?今天栈长我给大家整理了 Spring Boot 最核心的 25 个注解,都是干货! 你所需具备的基础 ...