秒殺系統設計的5個要點

程式設計師大彬發表於2023-01-12

本文已經收錄到Github倉庫,該倉庫包含計算機基礎、Java基礎、多執行緒、JVM、資料庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分散式、微服務、設計模式、架構等核心知識點,歡迎star~

Github地址:https://github.com/Tyson0314/...

秒殺系統涉及到的知識點

  • 高併發,cache,鎖機制
  • 基於快取架構redis,Memcached的先進先出佇列。
  • 稍微大一點的秒殺,肯定是分散式的叢集的,併發來自於多個節點的JVM,synchronized所有在JVM上加鎖是不行了
  • 資料庫壓力
  • 秒殺超賣問題
  • 如何防止使用者來刷, 黑名單?IP限制?
  • 利用memcached的帶原子性特性的操作做併發控制

秒殺簡單設計方案

比如有10件商品要秒殺,可以放到快取中,讀寫時不要加鎖。 當併發量大的時候,可能有25個人秒殺成功,這樣後面的就可以直接拋秒殺結束的靜態頁面。進去的25個人中有15個人是不可能獲得商品的。所以可以根據進入的先後順序只能前10個人購買成功。後面15個人就拋商品已秒殺完。

比如某商品10件物品待秒。假設有100臺web伺服器(假設web伺服器是Nginx + Tomcat),n臺app伺服器,n個資料庫

第一步 如果Java層做過濾,可以在每臺web伺服器的業務處理模組裡做個計數器AtomicInteger(10)=待秒商品總數,decreaseAndGet()>=0的繼續做後續處理,<0的直接返回秒殺結束頁面,這樣經過第一步的處理只剩下100臺*10個=1000個請求。

第二步,memcached 裡以商品id作為key的value放個10,每個web伺服器在接到每個請求的同時,向memcached伺服器發起請求,利用memcached的decr(key,1)操作返回值>=0的繼續處理,其餘的返回秒殺失敗頁面,這樣經過第二步的處理只剩下100臺中最快速到達的10個請求。

第三步,向App伺服器發起下單操作事務。

第四步,App伺服器向商品所在的資料庫請求減庫存操作(運算元據庫時可以 "update table set count=count-1 where id=商品id and count>0;" update 成功記錄數為1,再向訂單資料庫新增訂單記錄,都成功後提交整個事務,否則的話提示秒殺失敗,使用者進入支付流程。

看看淘寶的秒殺

一、前端

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

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

靜態化:將活動頁面上的所有可以靜態的元素全部靜態化,並儘量減少動態元素。透過CDN來抗峰值。

限流:一般都會採用IP級別的限流,即針對某一個IP,限制單位時間內發起請求數量。或者活動入口的時候增加遊戲或者問題環節進行消峰操作。

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

二、那麼後端的資料庫在高併發和超賣下會遇到什麼問題呢

  • 首先MySQL自身對於高併發的處理效能就會出現問題,一般來說,MySQL的處理效能會隨著併發thread上升而上升,但是到了一定的併發度之後會出現明顯的拐點,之後一路下降,最終甚至會比單thread的效能還要差。
  • 其次,超賣的根結在於減庫存操作是一個事務操作,需要先select,然後insert,最後update -1。最後這個-1操作是不能出現負數的,但是當多使用者在有庫存的情況下併發操作,出現負數這是無法避免的。
  • 最後,當減庫存和高併發碰到一起的時候,由於操作的庫存數目在同一行,就會出現爭搶InnoDB行鎖的問題,導致出現互相等待甚至死鎖,從而大大降低MySQL的處理效能,最終導致前端頁面出現超時異常。

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

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

II:修改原始碼,將排隊提到進入引擎層前,降低引擎層面的併發度。

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

解決方案1:將存庫從MySQL前移到Redis中,所有的寫操作放到記憶體中,由於Redis中不存在鎖故不會出現互相等待,並且由於Redis的寫效能和讀效能都遠高於MySQL,這就解決了高併發下的效能問題。然後透過佇列等非同步手段,將變化的資料非同步寫入到DB中。

優點:解決效能問題

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

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

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

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

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

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

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

解決方案4:將提交操作變成兩段式,先申請後確認。然後利用Redis的原子自增操作,同時利用Redis的事務特性來發號,保證拿到小於等於庫存閥值的號的人都可以成功提交訂單。然後資料非同步更新到DB中。

優點:解決超賣問題,庫存讀寫都在記憶體中,故同時解決效能問題。

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

總結

1、前端三板斧【擴容】【限流】【靜態化】

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

最後給大家分享一個Github倉庫,上面有大彬整理的300多本經典的計算機書籍PDF,包括C語言、C++、Java、Python、前端、資料庫、作業系統、計算機網路、資料結構和演算法、機器學習、程式設計人生等,可以star一下,下次找書直接在上面搜尋,倉庫持續更新中~

Github地址https://github.com/Tyson0314/...

相關文章