現(xiàn)在生活中,秒殺已經(jīng)隨處可見,尤其是在電商行業(yè)中。
這里,我將以自己的實(shí)際生活情況,學(xué)校圖書館搶購書包柜為例,講解一下秒殺系統(tǒng)。
秒殺系統(tǒng)分析
- 秒殺的場景決定了秒殺是一場速度的比拼,即“手快有、手慢無”?;顒?dòng)開始后,大家都瘋狂的點(diǎn)擊鼠標(biāo)。想在第一時(shí)間將書包柜搶到,完成預(yù)訂。因此秒殺活動(dòng)開始的一瞬間會(huì)有大量的流量涌入,幾倍、甚至于十幾倍的流量對(duì)系統(tǒng)的沖擊不可謂不大。如果系統(tǒng)沒有足夠的capacity或應(yīng)對(duì)措施,很可能就被瞬時(shí)高流量給壓垮了。
- 突如其來的高流量,給系統(tǒng)各個(gè)模塊都來了一連串的壓力,系統(tǒng)可能會(huì)因此變慢,而且可能會(huì)彼此影響,影響可用性。例如需要多次讀寫數(shù)據(jù)庫,隨著并發(fā)的壓力逐漸增大,數(shù)據(jù)庫更新的性能是逐漸下降的,進(jìn)而反饋到用戶的可能就是整個(gè)秒殺系統(tǒng)流程性能差、響應(yīng)慢。而面對(duì)響應(yīng)慢的系統(tǒng),很多用戶可能采取的措施就是反復(fù)刷新,多次嘗試,這無疑又增大了對(duì)系統(tǒng)的壓力。
上述種種給用戶帶來的往往是體驗(yàn)上的痛苦。如:網(wǎng)站響應(yīng)慢,點(diǎn)擊預(yù)訂按鈕沒反應(yīng)。好不容易可以操作了,卻發(fā)現(xiàn)秒殺活動(dòng)已經(jīng)結(jié)束。
在這里,我不得不吐嘈一下學(xué)校的書包柜預(yù)訂系統(tǒng),大學(xué)三年來,從來就沒有正常運(yùn)行過,一到搶購的那天深夜12點(diǎn),大家做的事就是等待一分鐘內(nèi)就崩潰的系統(tǒng)被修復(fù),而且因?yàn)椴恢浪囊幻氡恍迯?fù)好,很多人會(huì)等一夜。
基于以上秒殺場景下的痛點(diǎn)我在設(shè)計(jì)秒殺排隊(duì)系統(tǒng)在設(shè)計(jì)時(shí)主要考慮以下幾點(diǎn):
- 限流:當(dāng)秒殺活動(dòng)開始后,只有少部分消費(fèi)者能搶購到秒殺商品,意味著其實(shí)大部分用戶的流量傳達(dá)到后臺(tái)服務(wù)后都是無效。如果能引導(dǎo)這大部分的流量,不讓這大部分的流量傳達(dá)到后臺(tái)服務(wù),其實(shí)對(duì)我們系統(tǒng)的壓力就很小了。因此設(shè)計(jì)思路之一就是,僅讓能成功搶購到商品的流量(可以有一定余量)進(jìn)入我們的系統(tǒng)
- 削峰:進(jìn)入系統(tǒng)的有效流量雖然總量不一定是很大的,但卻是在很短的時(shí)間內(nèi)涌入的,因此會(huì)存在很高的瞬時(shí)流量峰值。總量相同的流量在1秒鐘進(jìn)入系統(tǒng),和在10分鐘均勻地進(jìn)入系統(tǒng),對(duì)系統(tǒng)的沖擊是相差很大的。高峰值的流量往往能將系統(tǒng)壓垮。因此另一個(gè)設(shè)計(jì)思路是,如何將進(jìn)入系統(tǒng)的瞬時(shí)高流量拉平,使得系統(tǒng)可以在自己處理能力范圍內(nèi),將所有搶購的請(qǐng)求處理完畢。
- 異步處理:傳統(tǒng)的系統(tǒng)對(duì)于請(qǐng)求是同步處理的,即收到請(qǐng)求后立即處理并把結(jié)果返回給用戶。我們的系統(tǒng)有了削峰的設(shè)計(jì)后,請(qǐng)求不是被立刻處理的,因此就要求我們能將同步的服務(wù)改造成異步的。
- 可用性:我們?cè)O(shè)計(jì)時(shí)始終把系統(tǒng)的可用性放在重要的位置,針對(duì)系統(tǒng)可能出現(xiàn)的各種狀況,都盡最大程度地保證高可用。
以下是我的設(shè)計(jì)思路:
代碼部分
- 對(duì)于客戶端請(qǐng)求進(jìn)行的初步處理,使用PHP代碼進(jìn)行編寫。
- 對(duì)客戶端請(qǐng)求進(jìn)行真正處理,即給用戶隨機(jī)分配箱子的代碼,使用JAVA進(jìn)行編寫。并使用隊(duì)列數(shù)據(jù)結(jié)構(gòu)和多線程進(jìn)行處理。
- 對(duì)于真正進(jìn)入Java進(jìn)行處理的請(qǐng)求,之前我們需要在PHP部分進(jìn)行兩次控制。第一次是隨機(jī)引導(dǎo)大部分的流量,不讓其進(jìn)入后臺(tái)服務(wù),并對(duì)失效的請(qǐng)求返回提示信息,提示信息為:請(qǐng)稍后重新刷新。第二次是查看書包柜是否還有剩余,若沒有剩余,就對(duì)此請(qǐng)求返回提示信息,提示信息為:書包柜已搶購?fù)戤叀?/li>
- 為了提高對(duì)數(shù)據(jù)庫的訪存速度,借助memcached key-value內(nèi)存存儲(chǔ)系統(tǒng)。將數(shù)據(jù)庫的存儲(chǔ)信息全部預(yù)存到memcached中。在memcached中數(shù)據(jù)的讀取與存儲(chǔ)速度遠(yuǎn)遠(yuǎn)高于mysql數(shù)據(jù)庫。
- 數(shù)據(jù)庫表結(jié)構(gòu),為了簡化只取兩列,表 箱子:ID和箱子具體信息。在本次討論中,ID取1~10000,代表有10000個(gè)箱子。預(yù)訂表: ID和用戶信息。
- 在第二次控制中,如何查看書包柜是否還有剩余,這是一個(gè)問題。顯然,如果每次都去讀后臺(tái)數(shù)據(jù)庫,那么我們討論的秒殺就沒有意義了。我們可以在memcached設(shè)置一個(gè)標(biāo)志位,初始值為0,表示箱子還有剩余,只有當(dāng)書包柜沒有剩余時(shí),更新此標(biāo)志位的值為1。在第二次控制的時(shí)候,查看書包柜是否還有剩余,我們只需對(duì)memcached中標(biāo)志位進(jìn)行讀取判斷即可。
實(shí)際執(zhí)行過程
- 為了限流,當(dāng)用戶在頁面點(diǎn)擊預(yù)訂按鈕后,請(qǐng)求首先進(jìn)入PHP代碼處理,PHP代碼第一部分用于隨機(jī)控制真正進(jìn)入Java代碼處理的請(qǐng)求。
對(duì)于不能進(jìn)入Java的請(qǐng)求,在PHP代碼里返回給用戶提示信息,提示其重新發(fā)起請(qǐng)求。 - 隨機(jī)限流之后,對(duì)memcached中標(biāo)志位進(jìn)行讀取進(jìn)行第二次控制,若為0,則將此請(qǐng)求放行,進(jìn)入Java代碼部分,若為1,則代表書包柜已被搶購?fù)戤?,PHP代碼返回給用戶提示信息,提示信息為:書包柜已搶購?fù)戤叀?/li>
- 經(jīng)過PHP兩次控制之后,真正能被處理的請(qǐng)求進(jìn)入Java代碼處理部分。在Java代碼中,使用一個(gè)隊(duì)列來存儲(chǔ)請(qǐng)求。當(dāng)隊(duì)列長度超過一定值后,就會(huì)對(duì)之后進(jìn)來的請(qǐng)求Java代碼進(jìn)行拒絕,并返回給PHP 代碼部分,PHP代碼部分再將拒絕結(jié)果返回給客戶端,要求其稍后重試。在JAVA代碼部分,使用多線程對(duì)請(qǐng)求進(jìn)行處理,以提高速度。
- 為了進(jìn)一步提高處理速度,可以采用多個(gè)隊(duì)列。每個(gè)隊(duì)列分配一定范圍內(nèi)的ID任務(wù),如第一個(gè)隊(duì)列負(fù)責(zé)1~2000的書包柜分配,第二個(gè)隊(duì)列負(fù)責(zé)2001~4000的書包柜分配任務(wù),以此類推。每次進(jìn)入java請(qǐng)求,隨機(jī)分配其進(jìn)入任一隊(duì)列,然后由此隊(duì)列按其任務(wù)順序分配書包柜。那么,當(dāng)有多個(gè)隊(duì)列時(shí),該如何設(shè)置memcached中的標(biāo)志位來表示書包柜是否搶購?fù)戤吥兀?br> 首先,初始值仍設(shè)為0,代表書包柜還有剩余。對(duì)于每個(gè)隊(duì)列,當(dāng)任務(wù)均已執(zhí)行完畢,如第一個(gè)隊(duì)列1~2000的書包柜均已分配完畢,則對(duì)memcached中的標(biāo)志位進(jìn)行一次更新加1,當(dāng)標(biāo)志位為5時(shí),代表書包柜分配完畢。在PHP的第二次控制中,對(duì)memcached中標(biāo)志位進(jìn)行讀取,若為0,則將此請(qǐng)求放行,進(jìn)入Java代碼部分,若為5,則代表書包柜已被搶購?fù)戤?,PHP代碼返回給用戶提示信息,提示信息為:書包柜已搶購?fù)戤?。在?zhí)行過程中,很可能某個(gè)的任務(wù)提前完成了,這時(shí)就可以將這個(gè)隊(duì)列消除。