Java秒殺系統(tǒng)(十二):JMeter壓力測(cè)試重現(xiàn)秒殺場(chǎng)景中超賣(mài)等問(wèn)題

作者: 修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。



摘要:本篇博文是“Java秒殺系統(tǒng)實(shí)戰(zhàn)系列文章”的第十二篇,本篇博文我們將借助壓力測(cè)試工具Jmeter重現(xiàn)秒殺場(chǎng)景(高并發(fā)場(chǎng)景)下出現(xiàn)的各種典型的問(wèn)題,其中最為經(jīng)典的當(dāng)屬“商品庫(kù)存超賣(mài)”的問(wèn)題,在本文我們重現(xiàn)這種問(wèn)題,并對(duì)問(wèn)題進(jìn)行分析!

內(nèi)容:一個(gè)正規(guī)的、聲稱(chēng)能承受高并發(fā)請(qǐng)求的系統(tǒng)的背后應(yīng)該經(jīng)歷了一些不為人知的經(jīng)歷,這個(gè)秒殺系統(tǒng)也是如此,一般而言,這些經(jīng)歷都是比較殘酷的,在本文中我們將重現(xiàn)出這樣的經(jīng)歷!即采用壓力測(cè)試工具Jmeter壓測(cè)這個(gè)秒殺系統(tǒng)的“秒殺接口”!

在進(jìn)入秒殺壓測(cè)環(huán)節(jié)前,我們將之前的“接收前端用戶(hù)的秒殺請(qǐng)求對(duì)應(yīng)的控制器方法”復(fù)制一份,用于給JMeter壓測(cè)使用,即在KillController中復(fù)制出一個(gè)新的“執(zhí)行秒殺請(qǐng)求”的方法,其代碼如下所示:

//商品秒殺核心業(yè)務(wù)邏輯-用于壓力測(cè)試
@RequestMapping(value = prefix+"/execute/lock",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public BaseResponse executeLock(@RequestBody @Validated KillDto dto, BindingResult result){
if (result.hasErrors() || dto.getKillId()<=0){
return new BaseResponse(StatusCode.InvalidParams);
}
BaseResponse response=new BaseResponse(StatusCode.Success);
try {
//不加分布式鎖的前提
Boolean res=killService.killItem(dto.getKillId(),dto.getUserId());
if (!res){
return new BaseResponse(StatusCode.Fail.getCode(),"不加分布式鎖-哈哈~商品已搶購(gòu)?fù)戤吇蛘卟辉趽屬?gòu)時(shí)間段哦!");
}
}catch (Exception e){
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
return response;
}

之后,我們便可以開(kāi)心的進(jìn)入玩耍環(huán)節(jié)。

(1) 雙擊JMeter的啟動(dòng)腳本jmeter.sh,進(jìn)入JMeter的主界面,新建一個(gè)測(cè)試計(jì)劃,然后在該測(cè)試計(jì)劃下新建一個(gè)線程組(設(shè)定1秒并發(fā)1000個(gè)線程,后續(xù)還可以調(diào)整線程數(shù)),緊接著是新建HTTP請(qǐng)求項(xiàng)以及CSV數(shù)據(jù)文件的讀取配置等等,如下圖所示:


其中,userId參數(shù)用于模擬參與秒殺~搶購(gòu)的用戶(hù),其取值將來(lái)源于上圖中的“CSV數(shù)據(jù)文件設(shè)置”選項(xiàng)的文件,在這里Debug設(shè)定了10個(gè)用戶(hù),如下圖所示:  


值得一提的,“HTTP消息頭管理器”選項(xiàng)是必需的,用于指定提交的數(shù)據(jù)的數(shù)據(jù)格式,即Content-Type的取值為application/json(因?yàn)槲覀兊暮蠖私涌谠O(shè)置的就是 consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)。

在開(kāi)始之前,我們?cè)O(shè)定了killId=3的商品作為秒殺~搶購(gòu)的對(duì)象,并在數(shù)據(jù)表中設(shè)定其“可搶購(gòu)數(shù)量/庫(kù)存”的值total為6,如下圖所示:


(2) 萬(wàn)事俱備只欠東風(fēng),下面我們點(diǎn)擊JMeter主界面的啟動(dòng),即可發(fā)起“1秒內(nèi)并發(fā)1000個(gè)線程”的請(qǐng)求,而這1000個(gè)線程對(duì)應(yīng)的用戶(hù)的Id,即userId將隨機(jī)從上述的CSV文件中讀取。在出現(xiàn)結(jié)果之前,我們先從理論的角度上進(jìn)行分析:10個(gè)用戶(hù)搶購(gòu)庫(kù)存只有6個(gè)的書(shū)籍,那么理論上結(jié)果應(yīng)該是“庫(kù)存變?yōu)?,被搶購(gòu)?fù)戤叄缓笤趇tem_kill_success表中會(huì)有6條,而且也應(yīng)該僅有6條秒殺成功的訂單記錄”! 然而,理論歸理論,現(xiàn)實(shí)還是很殘酷的!

(3) 點(diǎn)擊JMeter的啟動(dòng)按鈕,此時(shí)可以觀察控制臺(tái)的輸出信息以及數(shù)據(jù)庫(kù)表item_kill和item_kill_success,會(huì)發(fā)現(xiàn)一連串“慘不忍睹”的數(shù)據(jù)記錄,如下圖所示:


對(duì)于初次接觸“高并發(fā)秒殺業(yè)務(wù)場(chǎng)景”的童鞋可能會(huì)感覺(jué)到驚訝,“明明經(jīng)過(guò)Postman測(cè)試過(guò)了呀,為啥還會(huì)出現(xiàn)這種情況!”,有點(diǎn)百思不得其解!

然鵝呢,Debug想說(shuō)的是“事出必有因”,而出現(xiàn)這種情況,單單抱怨是屁用都木有的,還得去源頭進(jìn)行分析,即從代碼的層次進(jìn)行分析!

(4) 我們?cè)俅蝸?lái)回顧一下所寫(xiě)的“秒殺接口”的核心邏輯,如下圖所示:


A 當(dāng)用戶(hù)在前端界面瘋狂的點(diǎn)擊“搶購(gòu)”按鈕時(shí),我們上面接口將會(huì)接收到“洶涌潮水般”的用戶(hù)秒殺請(qǐng)求,首次秒殺,很多用戶(hù)都是第一次秒殺該商品,故而A流程大部分用戶(hù)都將通過(guò)考核;

B 同時(shí),由于B流程的邏輯是判斷是否可搶?zhuān)苊黠@,大家都是第一次來(lái)?yè)尩?,這個(gè)商品也沒(méi)那么快被搶完,故而B(niǎo)流程大家也將通過(guò)考核;

C 到了C流程,就需要扣減庫(kù)存了,由于庫(kù)存的扣減在這里只是單純的“減一”的操作,故而在C這個(gè)流程,很多人將可以成功減一;

D 最后大家勢(shì)如破竹,趕緊到了D流程,D流程是用于“生成秒殺成功的訂單”,記錄用戶(hù)秒殺過(guò)的商品的痕跡,同時(shí)也是為了服務(wù)于A流程;這個(gè)時(shí)候的D已經(jīng)不做什么判斷了(大家可以看到核心的判斷其實(shí)在于A流程,這也就是問(wèn)題出現(xiàn)的致命根源),大家就直接插入一條成功的記錄了。

因此,最終就出現(xiàn)了“庫(kù)存超賣(mài)”、“同一個(gè)用戶(hù)可以搶到多次”等各種莫名其妙的問(wèn)題;

通過(guò)上面的分析,其實(shí)Debug已經(jīng)指出來(lái)了,問(wèn)題產(chǎn)生的根源在于高并發(fā)的情況下D流程的處理并沒(méi)有為A流程的處理贏得足夠的時(shí)間,即“生成一條秒殺成功后的訂單記錄” 并沒(méi)有及時(shí)的為 “判斷用戶(hù)是否已經(jīng)秒殺過(guò)了~是否已經(jīng)有對(duì)應(yīng)的訂單記錄了” 的流程很好的服務(wù)!

那么在下面的篇章中,我們將從各個(gè)角度進(jìn)行優(yōu)化,包括數(shù)據(jù)庫(kù)級(jí)別Sql的優(yōu)化、代碼邏輯的優(yōu)化、分布式鎖的引入等等(當(dāng)然這些是從開(kāi)發(fā)的層面來(lái)講的,其實(shí)還有運(yùn)維的層面也可以?xún)?yōu)化,比如Nginx的負(fù)載均衡、中間件的集群部署提高高可用等等)!

補(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):