阿里黑科技雙十一億級(jí)并發(fā)下電商秒殺系統(tǒng)高并發(fā)如何防止庫(kù)存超賣

代碼量太大,錄制成視頻給大家。

一、秒殺帶來(lái)了什么?

在秒殺的情況下,肯定不能如此高頻率的去讀寫數(shù)據(jù)庫(kù),會(huì)嚴(yán)重造成性能問(wèn)題的

必須使用緩存,將需要秒殺的商品放入緩存中,并使用鎖來(lái)處理其并發(fā)情況。當(dāng)接到用戶秒殺提交訂單的情況下,先將商品數(shù)量遞減(加鎖/解鎖)后再進(jìn)行其他方面的處理,處理失敗在將數(shù)據(jù)遞增1(加鎖/解鎖),否則表示交易成功。

當(dāng)商品數(shù)量遞減到0時(shí),表示商品秒殺完畢,拒絕其他用戶的請(qǐng)求。

秒殺或搶購(gòu)活動(dòng)一般會(huì)經(jīng)過(guò)【預(yù)約】【搶訂單】【支付】這3個(gè)大環(huán)節(jié),而其中【搶訂單】這個(gè)環(huán)節(jié)是最考驗(yàn)業(yè)務(wù)提供方的抗壓能力的。

搶訂單環(huán)節(jié)一般會(huì)帶來(lái)2個(gè)問(wèn)題:

1、高并發(fā)

比較火熱的秒殺在線人數(shù)都是10w起的,如此之高的在線人數(shù)對(duì)于網(wǎng)站架構(gòu)從前到后都是一種考驗(yàn)。

2、超賣

任何商品都會(huì)有數(shù)量上限,如何避免成功下訂單買到商品的人數(shù)不超過(guò)商品數(shù)量的上限,這是每個(gè)搶購(gòu)活動(dòng)都要面臨的難題。

二、如何解決?

首先,產(chǎn)品解決方案我們就不予討論了。我們只討論技術(shù)解決方案

1、前端

面對(duì)高并發(fā)的搶購(gòu)活動(dòng),前端常用的三板斧是【擴(kuò)容】【靜態(tài)化】【限流】

A:擴(kuò)容

加機(jī)器,這是最簡(jiǎn)單的方法,通過(guò)增加前端池的整體承載量來(lái)抗峰值。

B:靜態(tài)化

將活動(dòng)頁(yè)面上的所有可以靜態(tài)的元素全部靜態(tài)化,并盡量減少動(dòng)態(tài)元素。通過(guò)CDN來(lái)抗峰值。

C:限流

一般都會(huì)采用IP級(jí)別的限流,即針對(duì)某一個(gè)IP,限制單位時(shí)間內(nèi)發(fā)起請(qǐng)求數(shù)量。

或者活動(dòng)入口的時(shí)候增加游戲或者問(wèn)題環(huán)節(jié)進(jìn)行消峰操作。

D:有損服務(wù)

最后一招,在接近前端池承載能力的水位上限的時(shí)候,隨機(jī)拒絕部分請(qǐng)求來(lái)保護(hù)活動(dòng)整體的可用性。

2、后端

那么后端的數(shù)據(jù)庫(kù)在高并發(fā)和超賣下會(huì)遇到什么問(wèn)題呢?主要會(huì)有如下3個(gè)問(wèn)題:(主要討論寫的問(wèn)題,讀的問(wèn)題通過(guò)增加cache可以很容易的解決)

I: 首先MySQL自身對(duì)于高并發(fā)的處理性能就會(huì)出現(xiàn)問(wèn)題,一般來(lái)說(shuō),MySQL的處理性能會(huì)隨著并發(fā)thread上升而上升,但是到了一定的并發(fā)度之后會(huì)出現(xiàn)明顯的拐點(diǎn),之后一路下降,最終甚至?xí)葐蝨hread的性能還要差。

II: 其次,超賣的根結(jié)在于減庫(kù)存操作是一個(gè)事務(wù)操作,需要先select,然后insert,最后update -1。最后這個(gè)-1操作是不能出現(xiàn)負(fù)數(shù)的,但是當(dāng)多用戶在有庫(kù)存的情況下并發(fā)操作,出現(xiàn)負(fù)數(shù)這是無(wú)法避免的。

III:最后,當(dāng)減庫(kù)存和高并發(fā)碰到一起的時(shí)候,由于操作的庫(kù)存數(shù)目在同一行,就會(huì)出現(xiàn)爭(zhēng)搶InnoDB行鎖的問(wèn)題,導(dǎo)致出現(xiàn)互相等待甚至死鎖,從而大大降低MySQL的處理性能,最終導(dǎo)致前端頁(yè)面出現(xiàn)超時(shí)異常。

針對(duì)上述問(wèn)題,如何解決呢? 我們先看眼淘寶的高大上解決方案:

I: 關(guān)閉死鎖檢測(cè),提高并發(fā)處理性能。

II:修改源代碼,將排隊(duì)提到進(jìn)入引擎層前,降低引擎層面的并發(fā)度。

III:組提交,降低server和引擎的交互次數(shù),降低IO消耗。

以上內(nèi)容可以參考丁奇在DTCC2013上分享的《秒殺場(chǎng)景下MySQL的低效》一文。在文中所有優(yōu)化都使用后,TPS在高并發(fā)下,從原始的150飆升到8.5w,提升近566倍,非常嚇人?。?!

不過(guò)結(jié)合我們的實(shí)際,改源碼這種高大上的解決方案顯然有那么一點(diǎn)不切實(shí)際。于是小伙伴們需要討論出一種適合我們實(shí)際情況的解決方案。以下就是我們討論的解決方案:

首先設(shè)定一個(gè)前提,為了防止超賣現(xiàn)象,所有減庫(kù)存操作都需要進(jìn)行一次減后檢查,保證減完不能等于負(fù)數(shù)。(由于MySQL事務(wù)的特性,這種方法只能降低超賣的數(shù)量,但是不可能完全避免超賣)

update number set x=x-1 where (x -1 ) >= 0;

解決方案1:

將存庫(kù)從MySQL前移到Redis中,所有的寫操作放到內(nèi)存中,由于Redis中不存在鎖故不會(huì)出現(xiàn)互相等待,并且由于Redis的寫性能和讀性能都遠(yuǎn)高于MySQL,這就解決了高并發(fā)下的性能問(wèn)題。然后通過(guò)隊(duì)列等異步手段,將變化的數(shù)據(jù)異步寫入到DB中。

1.用隊(duì)列解決大并發(fā)

建立一條隊(duì)列,將每個(gè)請(qǐng)求加入到隊(duì)列中,然后異步獲取隊(duì)列數(shù)據(jù)進(jìn)行處理,把多線程的事情變成單線程,處理完一個(gè)就從隊(duì)列中刪除一個(gè)。但是會(huì)出現(xiàn)一個(gè)現(xiàn)象,請(qǐng)求特別多的時(shí)候,一瞬間將redis隊(duì)列內(nèi)存撐爆,導(dǎo)致系統(tǒng)異常,又或者隊(duì)列內(nèi)存足夠大,也是一種方案,但是,系統(tǒng)處理完一個(gè)隊(duì)列內(nèi)請(qǐng)求的速度根本無(wú)法和瘋狂涌入隊(duì)列中的數(shù)目相比。也就是說(shuō),隊(duì)列內(nèi)的請(qǐng)求會(huì)越積累越多,最終Web系統(tǒng)平均響應(yīng)時(shí)候還是會(huì)大幅下降,系統(tǒng)還是陷入異常。

2.利用redis中基于cas的樂(lè)觀鎖

采用計(jì)數(shù)器的方式,用一個(gè)集合,存放每個(gè)商品以及其對(duì)應(yīng)的數(shù)量,如果只是單純的decr函數(shù)或者是incr函數(shù),不能解決秒殺這種問(wèn)題。因?yàn)橛锌赡茉诓l(fā)的情況下,兩個(gè)請(qǐng)求取到的數(shù)都是0,然后都加1,結(jié)果為1,實(shí)際上應(yīng)該是2。那么這個(gè)時(shí)候建議利用樂(lè)觀鎖,實(shí)現(xiàn)自己的decr函數(shù)。

樂(lè)觀鎖的機(jī)制如同版本控制,如果修改的時(shí)候,要修改的value在redis中的值已經(jīng)跟取出來(lái)時(shí)不一樣,則修改失敗。

秒殺開始之前,將庫(kù)存量放到Redis當(dāng)中,然后,對(duì)待每個(gè)請(qǐng)求,先判斷是否大于0,是的話,就去進(jìn)行庫(kù)存量減一操作,如果成功,則商品信息加入購(gòu)物車當(dāng)中,如果失敗,則不能加入到購(gòu)物車,返回秒殺失??;小于0則直接返回秒殺失敗。

優(yōu)點(diǎn):解決性能問(wèn)題

缺點(diǎn):沒(méi)有解決超賣問(wèn)題,同時(shí)由于異步寫入DB,存在某一時(shí)刻DB和Redis中數(shù)據(jù)不一致的風(fēng)險(xiǎn)。

解決方案2:

引入隊(duì)列,然后將所有寫DB操作在單隊(duì)列中排隊(duì),完全串行處理。當(dāng)達(dá)到庫(kù)存閥值的時(shí)候就不在消費(fèi)隊(duì)列,并關(guān)閉購(gòu)買功能。這就解決了超賣問(wèn)題。

優(yōu)點(diǎn):解決超賣問(wèn)題,略微提升性能。

缺點(diǎn):性能受限于隊(duì)列處理機(jī)處理性能和DB的寫入性能中最短的那個(gè),另外多商品同時(shí)搶購(gòu)的時(shí)候需要準(zhǔn)備多條隊(duì)列。

解決方案3:

將寫操作前移到Memcached中,同時(shí)利用Memcached的輕量級(jí)的鎖機(jī)制CAS來(lái)實(shí)現(xiàn)減庫(kù)存操作。

優(yōu)點(diǎn):讀寫在內(nèi)存中,操作性能快,引入輕量級(jí)鎖之后可以保證同一時(shí)刻只有一個(gè)寫入成功,解決減庫(kù)存問(wèn)題。

缺點(diǎn):沒(méi)有實(shí)測(cè),基于CAS的特性不知道高并發(fā)下是否會(huì)出現(xiàn)大量更新失???不過(guò)加鎖之后肯定對(duì)并發(fā)性能會(huì)有影響。

解決方案4:

將提交操作變成兩段式,先申請(qǐng)后確認(rèn)。然后利用Redis的原子自增操作(相比較MySQL的自增來(lái)說(shuō)沒(méi)有空洞),同時(shí)利用Redis的事務(wù)特性來(lái)發(fā)號(hào),保證拿到小于等于庫(kù)存閥值的號(hào)的人都可以成功提交訂單。然后數(shù)據(jù)異步更新到DB中。

優(yōu)點(diǎn):解決超賣問(wèn)題,庫(kù)存讀寫都在內(nèi)存中,故同時(shí)解決性能問(wèn)題。

缺點(diǎn):由于異步寫入DB,可能存在數(shù)據(jù)不一致。另可能存在少買,也就是如果拿到號(hào)的人不真正下訂單,可能庫(kù)存減為0,但是訂單數(shù)并沒(méi)有達(dá)到庫(kù)存閥值。

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群。交流學(xué)習(xí)群號(hào):938837867 暗號(hào):555 里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容