【高併發】秒殺業務場景詳解

一、秒殺場景的特點

       秒殺的商品具有價格低、庫存有限、定時開始的特點,因此秒殺場景最大的特點就是高併發。數以千萬的用戶的流量集中在某個時間點上(即秒殺開始時),給後端服務器造成很大壓力,如果不能進行有效削峯、限流,所有請求一次性打到某一臺服務器或數據庫上,必然造成服務的不可用,給用戶造成不良體驗。

二、整體架構設計

(img-rcwcAugm-1592300370556)(https://github.com/Yiwei-Lin/SecKillShop/blob/master/pics/image-20200612215327156.png?raw=true)]
       在整體架構上,用戶的請求首先通過一個網關進行負載均衡,根據負載均衡算法的不同,應用不同的策略將請求轉發給對應的應用服務器,服務器對用戶的請求進行限流,拒絕大部分的請求。根據Redis中緩存的商品信息,判斷當前秒殺是否已結束,如果秒殺結束,拒接請求並設置內存標記,後續的請求直接拒絕,無需再查詢Redis緩存;如果秒殺未結束,將請求入隊MQ,進行異步處理,進一步削峯。
       多個消費者從MQ中取消息,進行處理,即檢查對應商品的庫存情況,減庫存並生成訂單,要注意這裏需要原子操作的控制,也有多種解決方案,在後面會詳細討論。對秒殺成功的訂單,設置有效期,用戶在有效期內成功付款,則生成支付訂單,持久化到MySQL,否則會隨着Redis緩存中key的過期而失效。

三、秒殺系統常見的問題

       秒殺系統由於高併發的特點,會帶來一系列問題,這些問題不僅是秒殺系統獨有,任何高併發系統都需要考慮這些問題的解決方案。

  • 高併發帶來的響應緩慢甚至服務不可用

       MySQL由於數據都存放在磁盤中,併發量十分有限,並且爲了保證業務邏輯的正確性,每個請求都需要對商品數據進行一次加鎖和解鎖操作(行鎖的效率低),更加降低了效率,造成不好的用戶體驗。更糟的情況是MySQL在高併發環境下,還可能會崩潰,造成服務不可用。

  • 超賣、少賣

       超賣問題來源於覈減庫存的操作其實不具備原子性,它分爲了三步:查詢庫存->檢查庫存不爲0->扣減庫存。那麼設想一個場景:當服務查詢庫存爲1,但還未扣減庫存時,另一個服務查詢庫存也爲1,兩個服務都會進行扣減庫存、生成訂單的操作,造成了超賣。
       少賣問題來源於已經成功扣減庫存,但生成秒殺訂單因爲各種原因失敗了,導致庫存被扣減卻沒有訂單生成的情況。該問題出現的根本原因是扣減庫存和生成訂單兩步操作沒有保證原子性。

  • 用戶作弊

       某些不法用戶可能通過自動化腳本模擬HTTP請求的方式反覆刷秒殺接口,既對系統造成了更大的流量壓力也產生了造成了不公平。

四、優化方案

  • 將大部分請求攔截在上游

       通過隨機算法、哈希算法或根據當前負載情況動態地攔截請求,快速失敗,只將少部分的請求放入MQ等待消費者消費。

  • 驗證碼機制

       通過驗證碼機制能夠很好限制用戶的請求速度,同時也能防止作弊。驗證碼的選擇上可以選擇圖片、算式、滑塊等,爲了防止驗證碼識別工具,儘可能選擇較複雜的驗證碼。

  • 限制用戶的每秒請求次數

       每次用戶請求後在緩存中進行計數,並設置相應有效期,當用戶請求達到閾值直接拒絕請求,實現限制單用戶每秒請求次數的功能,防止單一用戶的高頻訪問給系統造成更大壓力,這個策略根據業務需求的不同可對賬號的限制、IP的限制或賬號和IP共同限制。

  • 頁面靜態化、頁面緩存

       通過頁面靜態化、頁面緩存的方式降低響應的時間。
       頁面靜態化是進行頁面緩存的第一步,將一個動態頁面分離成靜態的頁面模板和動態的數據部分,將靜態的部分拆分出來進行緩存,動態的部分根據服務器的業務處理返回json數據再進行渲染。比如在秒殺頁面,頁面的模板框架是靜態資源,可以進行緩存,而商品名稱、庫存數量、價格、秒殺剩餘時間、驗證碼等信息根據請求的響應結果進行動態渲染。
       靜態頁面抽離出來之後就要進行緩存,緩存可以放在用戶瀏覽器、服務端或CDN,放在瀏覽器上的緩存具有不可控性,如果用戶不進行及時刷新,很可能看到錯誤的不一致信息,對於秒殺系統而言,信息的實時性非常重要,這點看來放在瀏覽器上的緩存並不合適。另外服務端主要進行業務邏輯的計算和加載,不擅長處理大量連接,如果進行頁面的緩存和加載會帶來性能的降低。因此頁面緩存常放在CDN上。
在這裏插入圖片描述
       CDN的節點一般選擇訪問量集中的地區附近且要保證節點與主站之間的網絡通信,即考慮緩存的命中率和及時失效的問題,這點對於高併發、信息變化快的秒殺系統而言及其重要。

  • 利用MQ異步削峯

       大量的請求並不直接到達應用服務器,而是先進入MQ隊列,多個消費者根據處理能力從MQ中拿到請求消息並進行業務處理,類似於著名的“漏桶算法”。MQ還可以通過設置最大隊列的長度或消息的有效期TTL來進行限流,通過設置消息超時快速失敗,防止大量的消息堆積給用戶帶來不好的體驗。

  • Redis緩存提高併發量
           Redis作爲一個內存數據庫比MySQL的QPS高100倍以上,並且由於底層操作處理是單線程的,在高併發、分佈式架構下無需反覆的加鎖、解鎖和線程上下文的切換,更進一步提高了效率。通常將熱點數據緩存到Redis中,比如秒殺系統中商品的ID、庫存等信息,秒殺過程中進行覈減庫存等操作都是在Redis中,能顯著提高效率。Redis通常以集羣部署且設置若干哨兵以保證服務的高可用性。

  • 定時任務進行緩存預熱
           秒殺場景比較特殊(相較於微博熱搜等熱點數據而言),大流量會集中在秒殺開始的時間點上,如果此時緩存中沒有相關商品數據,會導致瞬間請求全部轉向數據庫,造成緩存擊穿。因此,秒殺場景的緩存預熱非常有必要,由於秒殺都有一個確定的開始時間,可以通過定時任務在秒殺開始前的某個時刻將商品的相關信息預熱到緩存中,這樣當秒殺開始時,在緩存中就有相關的數據了,這個定時任務可以不需要非常頻繁進行檢查,性能消耗並不大,比如規定在秒殺開始前5分鐘進行緩存預熱,那麼每1分鐘進行1次檢查完全足夠。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章