天天看點

如何解決秒殺的性能問題和超賣的讨論

進入正文前先說一點個人感受,之前看淘寶的ppt感覺都懂了,等到自己出解決方案的時候發現還是有很多想不到的地方其實都沒懂,再次驗證了“細節是魔鬼”的理論。并且一個人的能力有限,隻有大家一起讨論才能想的更周全,更細緻。好了,閑話少說,下面進入正文。

一、秒殺帶來了什麼?

秒殺或搶購活動一般會經過【預約】【搶訂單】【支付】這3個大環節,而其中【搶訂單】這個環節是最考驗業務提供方的抗壓能力的。

搶訂單環節一般會帶來2個問題:

  1、高并發

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

  2、超賣

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

 二、如何解決?

 首先,産品解決方案我們就不予讨論了。我們隻讨論技術解決方案

1、前端

面對高并發的搶購活動,前端常用的三闆斧是【擴容】【靜态化】【限流】

  A:擴容

  加機器,這是最簡單的方法,通過增加前端池的整體承載量來抗峰值。

  B:靜态化

  将活動頁面上的所有可以靜态的元素全部靜态化,并盡量減少動态元素。通過          CDN來抗峰值。

  C:限流

  一般都會采用IP級别的限流,即針對某一個IP,限制機關時間内發起請求數量。

  或者活動入口的時候增加遊戲或者問題環節進行消峰操作。

  D:有損服務

  最後一招,在接近前端池承載能力的水位上限的時候,随機拒絕部分請求來保護活動整體的可用性。

 2、後端

那麼後端的資料庫在高并發和超賣下會遇到什麼問題呢?主要會有如下3個問題:(主要讨論寫的問題,讀的問題通過增加cache可以很容易的解決)

  I: 首先MySQL自身對于高并發的處理性能就會出現問題,一般來說,MySQL的處理性能會随着并發thread上升而上升,但是到了一定的并發度之後會出現明顯的拐點,之後一路下降,最終甚至會比單thread的性能還要差。

  II: 其次,超賣的根結在于減庫存操作是一個事務操作,需要先select,然後insert,最後update -1。最後這個-1操作是不能出現負數的,但是當多使用者在有庫存的情況下并發操作,出現負數這是無法避免的。

  III:最後,當減庫存和高并發碰到一起的時候,由于操作的庫存數目在同一行,就會出現争搶InnoDB行鎖的問題,導緻出現互相等待甚至死鎖,進而大大降低MySQL的處理性能,最終導緻前端頁面出現逾時異常。

針對上述問題,如何解決呢? 我們先看眼淘寶的高大上解決方案:

  I:  關閉死鎖檢測,提高并發處理性能。

  II:修改源代碼,将排隊提到進入引擎層前,降低引擎層面的并發度。

  III:組送出,降低server和引擎的互動次數,降低IO消耗。

以上内容可以參考丁奇在DTCC2013上分享的《秒殺場景下MySQL的低效》一文。在文中所有優化都使用後,TPS在高并發下,從原始的150飙升到8.5w,提升近566倍,非常吓人!!!

 不過結合我們的實際,改源碼這種高大上的解決方案顯然有那麼一點不切實際。于是小夥伴們需要讨論出一種适合我們實際情況的解決方案。以下就是我們讨論的解決方案:

首先設定一個前提,為了防止超賣現象,所有減庫存操作都需要進行一次減後檢查,保證減完不能等于負數。(由于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,但是訂單數并沒有達到庫存閥值。

 三、總結

 1、前端三闆斧【擴容】【限流】【靜态化】

 2、後端兩條路【記憶體】+【排隊】

 四、非技術感想

1、團隊的力量是無窮的,各種各樣的解決方案(先不談可行性)都是在小夥伴們七嘴八舌中讨論出來的。我們需要讓所有人都發出自己的聲音,不要着急去否定。

2、優化需要從整體層面去思考,不要隻糾結于自己負責的部分,如果隻盯着一個點思考,最後很可能就走進死胡同中了。

3、有很多東西以為讀過了就懂了,其實不然。依然還是需要實踐,否則别人的知識永遠不可能變成自己的。