前言
上一篇在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
