Java秒殺系統(tǒng)(十八):秒殺邏輯優(yōu)化之RabbitMQ接口限流二
作者:
修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。
摘要:本篇博文是“Java秒殺系統(tǒng)實(shí)戰(zhàn)系列文章”的第十八篇,我們將繼續(xù)秒殺系統(tǒng)的優(yōu)化之路。在本篇文章中我們將基于RabbitMQ異步通信、FIFO(先進(jìn)先出)、接口限流的特性,在執(zhí)行秒殺核心的處理邏輯之前架上一層“限流”的處理邏輯,從而讓瞬時(shí)產(chǎn)生的,猶如波濤洶涌、潮水般的請(qǐng)求流量變得井井有條、有序性地到達(dá)后端的秒殺接口!
內(nèi)容:接著上一篇章的講解,我們需要在后端 接收前端高并發(fā)產(chǎn)生多線程請(qǐng)求時(shí),及時(shí)高效地轉(zhuǎn)移巨大的用戶請(qǐng)求之MQ中間件中,為后端秒殺接口贏得足夠的、規(guī)范化的處理!在這一過(guò)程,前端和后端的交互是異步的,因此,在前后端處理邏輯層面跟前面篇章的處理方式將有所不同。
(1)首先,在Controller層,需要提供響應(yīng)前端秒殺請(qǐng)求的方法,該方法不直接處理秒殺的核心業(yè)務(wù)邏輯,而是將其轉(zhuǎn)移至MQ中間件中,并立即返回success的狀態(tài)信息給回到前端,其代碼如下所示:
@Autowired
private RabbitSenderService rabbitSenderService;
//商品秒殺核心業(yè)務(wù)邏輯-mq限流
@RequestMapping(value = prefix+"/execute/mq",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public BaseResponse executeMq(@RequestBody @Validated KillDto dto, BindingResult result, HttpSession session){
if (result.hasErrors() || dto.getKillId()<=0){
return new BaseResponse(StatusCode.InvalidParams);
}
Object uId=session.getAttribute("uid");
if (uId==null){
return new BaseResponse(StatusCode.UserNotLogin);
}
Integer userId= (Integer)uId ;
BaseResponse response=new BaseResponse(StatusCode.Success);
Map<String,Object> dataMap= Maps.newHashMap();
try {
dataMap.put("killId",dto.getKillId());
dataMap.put("userId",userId);
response.setData(dataMap);
dto.setUserId(userId);
rabbitSenderService.sendKillExecuteMqMsg(dto);
}catch (Exception e){
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
return response;
}
(2)前端info.jsp再提交秒殺請(qǐng)求并接收到后端的返回信息后,便立即跳轉(zhuǎn)至相應(yīng)的頁(yè)面,即秒殺結(jié)果查看頁(yè)(準(zhǔn)備查看相應(yīng)的秒殺結(jié)果的),該頁(yè)面是通過(guò)響應(yīng)后端Controller器方法進(jìn)行跳轉(zhuǎn)的,其頁(yè)面的js代碼如下所示:
function executeKillMq() {
$.ajax({
type: "POST",
url: "${ctx}/kill/execute/mq",
contentType: "application/json;charset=utf-8",
data: JSON.stringify(getJsonData()),
dataType: "json",
success: function(res){
if (res.code==0) {
//立即跳轉(zhuǎn)至“秒殺結(jié)果查看頁(yè)”
window.location.href="${ctx}/kill/execute/mq/to/result?killId="+$("#killId").val()
}else{
window.location.href="${ctx}/kill/execute/fail"
}
},
error: function (message) {
alert("提交數(shù)據(jù)失敗!");
return;
}
});
}
其中,Controller對(duì)應(yīng)的跳轉(zhuǎn)頁(yè)面的方法代碼如下所示:
//商品秒殺核心業(yè)務(wù)邏輯-mq限流-立馬跳轉(zhuǎn)至搶購(gòu)結(jié)果頁(yè)
@RequestMapping(value = prefix+"/execute/mq/to/result",method = RequestMethod.GET)
public String executeToResult(@RequestParam Integer killId,HttpSession session,ModelMap modelMap){
Object uId=session.getAttribute("uid");
if (uId!=null){
Integer userId= (Integer)uId ;
modelMap.put("killId",killId);
modelMap.put("userId",userId);
}
return "executeMqResult";
}
其中executeMqResult.jsp主要用于查看當(dāng)前用戶對(duì)于當(dāng)前商品的秒殺結(jié)果,頁(yè)面代碼比較簡(jiǎn)單,在這里就不貼出來(lái)了;下面只貼出其發(fā)起查詢秒殺結(jié)果的js請(qǐng)求代碼,如下所示:
<script type="text/javascript">
$(function () {
//等待一定的時(shí)間再查詢顯示結(jié)果-給后端贏得足夠的時(shí)間
setTimeout(showResult,5000);
});
function showResult() {
var killId=$("#killId").val();
var userId=$("#userId").val();
$.ajax({
type: "GET",
url: "${ctx}/kill/execute/mq/result?killId="+killId+"&userId="+userId,
success: function(res){
if (res.code==0) {
$("#executeResult").html(res.data.executeResult);
$("#waitResult").html("");
}else{
$("#executeResult").html(res.msg);
}
},
error: function (message) {
alert("提交數(shù)據(jù)失??!");
return;
}
});
}
</script>
其對(duì)應(yīng)的Controller的請(qǐng)求方法如下所示:
//商品秒殺核心業(yè)務(wù)邏輯-mq限流-在搶購(gòu)結(jié)果頁(yè)中發(fā)起搶購(gòu)結(jié)果的查詢
@RequestMapping(value = prefix+"/execute/mq/result",method = RequestMethod.GET)
@ResponseBody
public BaseResponse executeResult(@RequestParam Integer killId,@RequestParam Integer userId){
BaseResponse response=new BaseResponse(StatusCode.Success);
try {
Map<String,Object> resMap=killService.checkUserKillResult(killId,userId);
response.setData(resMap);
}catch (Exception e){
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
return response;
}
(3)其中,killService.checkUserKillResult(killId,userId);方法的功能主要是根據(jù)killId和userId在item_kill_success表查詢用戶的秒殺結(jié)果,其源代碼如下所示:
//檢查用戶的秒殺結(jié)果
@Override
public Map<String,Object> checkUserKillResult(Integer killId, Integer userId) throws Exception {
Map<String,Object> dataMap= Maps.newHashMap();
KillSuccessUserInfo info=itemKillSuccessMapper.selectByKillIdUserId(killId,userId);
if (info!=null){
dataMap.put("executeResult",String.format(env.getProperty("notice.kill.item.success.content"),info.getItemName()));
dataMap.put("info",info);
}else{
throw new Exception(env.getProperty("notice.kill.item.fail.content"));
}
return dataMap;
}
而itemKillSuccessMapper.selectByKillIdUserId(killId,userId);對(duì)應(yīng)的動(dòng)態(tài)Sql的寫(xiě)法如下所示:
<!--根據(jù)秒殺成功后killId+userId的訂單編碼查詢-->
<select id="selectByKillIdUserId" resultType="com.debug.kill.model.dto.KillSuccessUserInfo">
SELECT
a.*,
b.user_name,
b.phone,
b.email,
c.name AS itemName
FROM item_kill_success AS a
LEFT JOIN user b ON b.id = a.user_id
LEFT JOIN item c ON c.id = a.item_id
WHERE a.kill_id=#{killId} AND a.user_id=#{userId}
AND b.is_active = 1
</select>
至此,關(guān)于RabbitMQ的接口限流篇章我們也就介紹完畢了,下面給大家展示一下整體的效果!
(1)首先當(dāng)然是搶購(gòu)頁(yè)啦!為了區(qū)別之前的“搶購(gòu)”,我們加上了一個(gè)新按鈕,“搶購(gòu)-MQ異步”:
(2)點(diǎn)擊“搶購(gòu)-MQ異步”按鈕,前端將立即跳轉(zhuǎn)至“搶購(gòu)結(jié)果等待頁(yè)”,如下圖所示:
(3)等待一定的時(shí)間之后發(fā)起查詢“秒殺結(jié)果”的請(qǐng)求,最終即可在頁(yè)面顯示秒殺的結(jié)果,如下圖所示:
(4)當(dāng)然,Debug還提供了一個(gè)用于JMeter壓測(cè)的請(qǐng)求方法,代碼在這里就不貼出來(lái),可以點(diǎn)擊文末提供的鏈接前往查看!不過(guò),值得一貼的是Debug親自壓測(cè)過(guò)后的效果圖,如下圖所示:
至此,關(guān)于秒殺系統(tǒng)的優(yōu)化(還有之前介紹過(guò)的分布式唯一ID、業(yè)務(wù)服務(wù)模塊異步解耦、用戶認(rèn)證、郵件通知等也是其中的優(yōu)化項(xiàng))之路我們就暫時(shí)到這里了。值得一提的是,各位小伙伴會(huì)發(fā)現(xiàn)我們做的這些優(yōu)化大部分是“開(kāi)發(fā)層面”的,而事實(shí)上,在“運(yùn)維層面”也是大有文章可做的,比如我們可以采取如下的措施:
A 使用中間件的集群提供服務(wù)的高可用,比如Redis集群、ZooKeeper集群、RabbitMQ集群等等
B Nginx集群、實(shí)現(xiàn)負(fù)載均衡,并從服務(wù)器的層面實(shí)現(xiàn)初步限流
C 數(shù)據(jù)庫(kù)Mysql做主備部署,實(shí)現(xiàn)讀寫(xiě)分離,即一個(gè)Master,多個(gè)Slave,其中Master充當(dāng)寫(xiě)角色、Slave充當(dāng)讀角色,提供數(shù)據(jù)庫(kù)層面的操作效率。當(dāng)然,還有很多很多,各位小伙伴有啥好的建議或者方案都可以拿出來(lái)提一提,或者加入技術(shù)群討論討論都是OK的!
補(bǔ)充:
1、目前,這一秒殺系統(tǒng)的整體構(gòu)建與代碼實(shí)戰(zhàn)已經(jīng)全部完成了,該秒殺系統(tǒng)對(duì)應(yīng)的視頻教程的鏈接地址為:https://www.fightjava.com/web/index/course/detail/6,可以點(diǎn)擊鏈接進(jìn)行試看以及學(xué)習(xí),實(shí)戰(zhàn)期間有任何問(wèn)題都可以留言或者與Debug聯(lián)系、交流!
2、另外,Debug也開(kāi)源了該秒殺系統(tǒng)對(duì)應(yīng)的完整的源代碼以及數(shù)據(jù)庫(kù),其地址可以來(lái)這里下載:https://gitee.com/steadyjack/SpringBoot-SecondKill 記得Fork跟Star?。。?!
3、最后,不要忘記了關(guān)注一下Debug的技術(shù)微信公眾號(hào)