關於秒殺的系統架構優化思路


一、問題的提出

秒殺或搶購活動一般會經過 預約,下單,支付 ,扛不住的地方在於下單,一般會帶來2個問題:

1、高並發

比較火熱的秒殺在線人數都是10w起的,如此之高的在線人數對於網站架構從前到后都是一種考驗。

2、超賣

任何商品都會有數量上限,如何避免成功下訂單買到商品的人數不超過商品數量的上限,這是每個搶購活動都要面臨的難題。

秒殺系統難做的原因:庫存只有一份,瞬間大量用戶讀和寫這些數據。

例如小米手機每周二的秒殺,可能手機只有1萬部,但瞬時進入的流量可能是幾百幾千萬

二、架構

常見站點架構如下

1)瀏覽器端,最上層,會執行到一些JS代碼

2)站點層,這一層會訪問后端數據,返回數據給瀏覽器

3)服務層,向上游屏蔽底層數據細節

4)數據層,最終的庫存是存在這里的

三、優化思路

1、將請求盡量攔截在上游:傳統秒殺系統之所以掛,請求都壓倒了后端數據層,數據庫讀寫鎖沖突嚴重,導致響應慢,下單基本不能成功

2、利用緩存:這是一個典型的讀多些少的應用場景,非常適合使用緩存

四、優化細節

1 、瀏覽器層請求攔截

a)產品層面,用戶點擊“查詢”或者“購票”后,按鈕置灰,禁止用戶重復提交請求

b)js層面,限制用戶在x秒之內只能提交一次請求

可以攔截很多無效請求

2、站點層請求攔截與頁面緩存

防止像服務器直接發送過多的惡意http請求

a)同一個uid,限制訪問頻度,做頁面緩存,x秒內到達站點層的請求,均返回同一頁面

b)同一個item的查詢,例如手機車次,做頁面緩,x秒內到達站點層的請求,均返回同一頁面

可以攔截很多無效請求

3、服務層請求攔截與數據緩存

a)給過多的請求去數據庫有什么意義呢?對於寫請求,做請求隊列,每次只透過有限的寫請求去數據層,如果均成功再放下一批,如果庫存不夠則隊列里的寫請求全部返回“已售完

b)對於讀請求cache來抗,用memcached or redis(10wqps)

如此限流,只有非常少的寫請求,和非常少的讀緩存mis的請求會透到數據層去

4、數據層閑庭信步

到了數據這一層,幾乎就沒有什么請求了,庫存是有限的,透過過多請求來數據庫沒有意義

五、解決方案

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

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

解決方案1:

將存庫從MySQL前移到Redis中,所有的寫操作放到內存中,由於Redis中不存在鎖故不會出現互相等待,並且由於Redis的寫性能和讀性能都遠高於MySQL,這就解決了高並發下的性能問題。然后通過隊列等異步手段,將變化的數據異步寫入到DB中。

優點:解決性能問題

缺點:沒有解決超賣問題,同時由於異步寫入DB,存在某一時刻DB和Redis中數據不一致的風險。

 

解決方案2:

引入隊列,然后將所有寫DB操作在單隊列中排隊,完全串行處理。當達到庫存閥值的時候就不在消費隊列,並關閉購買功能。這就解決了超賣問題。

優點:解決超賣問題,略微提升性能。

缺點:性能受限於隊列處理機處理性能和DB的寫入性能中最短的那個,另外多商品同時搶購的時候需要准備多條隊列。

 

解決方案3:

將寫操作前移到MC中,同時利用MC的輕量級的鎖機制CAS來實現減庫存操作。

優點:讀寫在內存中,操作性能快,引入輕量級鎖之后可以保證同一時刻只有一個寫入成功,解決減庫存問題。

缺點:沒有實測,基於CAS的特性不知道高並發下是否會出現大量更新失敗?不過加鎖之后肯定對並發性能會有影響。

 

解決方案4:

將提交操作變成兩段式,先申請后確認。然后利用Redis的原子自增操作(相比較MySQL的自增來說沒有空洞),同時利用Redis的事務特性來發號,保證拿到小於等於庫存閥值的號的人都可以成功提交訂單。然后數據異步更新到DB中。

優點:解決超賣問題,庫存讀寫都在內存中,故同時解決性能問題。

缺點:由於異步寫入DB,可能存在數據不一致。另可能存在少買,也就是如果拿到號的人不真正下訂單,可能庫存減為0,但是訂單數並沒有達到庫存閥值。

 

總結

擴容+限流+內存緩存+排隊

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM