秒殺或搶購活動通常會通過 預定,下單,支付 ,扛不住的地方在於下單,通常會帶來2個問題:redis
一、高併發sql
比較火熱的秒殺在線人數都是10w起的,如此之高的在線人數對於網站架構從前到後都是一種考驗。數據庫
二、超賣後端
任何商品都會有數量上限,如何避免成功下訂單買到商品的人數不超過商品數量的上限,這是每一個搶購活動都要面臨的難題。瀏覽器
秒殺系統難作的緣由:庫存只有一份,瞬間大量用戶讀和寫這些數據。緩存
例如小米手機每週二的秒殺,可能手機只有1萬部,但瞬時進入的流量多是幾百幾千萬服務器
常見站點架構以下架構
1)瀏覽器端,最上層,會執行到一些JS代碼併發
2)站點層,這一層會訪問後端數據,返回數據給瀏覽器異步
3)服務層,向上遊屏蔽底層數據細節
4)數據層,最終的庫存是存在這裏的
一、將請求儘可能攔截在上游:傳統秒殺系統之因此掛,請求都壓倒了後端數據層,數據庫讀寫鎖衝突嚴重,致使響應慢,下單基本不能成功
二、利用緩存:這是一個典型的讀多些少的應用場景,很是適合使用緩存
4、優化細節
a)產品層面,用戶點擊「查詢」或者「購票」後,按鈕置灰,禁止用戶重複提交請求
b)js層面,限制用戶在x秒以內只能提交一次請求
能夠攔截不少無效請求
防止像服務器直接發送過多的惡意http請求
a)同一個uid,限制訪問頻度,作頁面緩存,x秒內到達站點層的請求,均返回同一頁面
b)同一個item的查詢,例如手機車次,作頁面緩存,x秒內到達站點層的請求,均返回同一頁面
能夠攔截不少無效請求
a)給過多的請求去數據庫有什麼意義呢?對於寫請求,作請求隊列,每次只透過有限的寫請求去數據層,若是均成功再放下一批,若是庫存不夠則隊列裏的寫請求所有返回「已售完
b)對於讀請求,cache來抗,用memcached or redis(10wqps)
如此限流,只有很是少的寫請求,和很是少的讀緩存mis的請求會透到數據層去
到了數據這一層,幾乎就沒有什麼請求了,庫存是有限的,透過過多請求來數據庫沒有意義
關於超賣,首先設定一個前提,爲了防止超賣現象,全部減庫存操做都須要進行一次減後檢查,保證減完不能等於負數。(因爲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,可是訂單數並無達到庫存閥值。
總結
擴容+限流+內存緩存+排隊