高併發業務下的無損技術方案設計

公众号-JavaEdge發表於2024-08-25

0 前言

秒殺,既有需求真實且迫切的使用者,也有試圖牟利的黃牛。系統挑戰,就是相較於以往千倍萬倍的使用者規模,可能是真人可能是機器人,在同一瞬間對系統發起衝擊,需要海量的計算資源才能支撐。

秒殺系統的設計套路往往適用於其他高併發場景,具有較高的借鑑價值。同時,其特殊的挑戰和需求,需要架構師在設計中權衡考量,這也有助於培養個人在權衡取捨方面的能力。

1 無損技術方案

好比治水:

  • 分流,讓支流分攤壓力,隔離風險。軟體設計就是系統隔離,分割流量

  • 建水庫儲存洪水,再緩慢排出,削峰填谷。軟體設計就是無損消峰

  • 拓寬河道,清除淤沙提高水流流速。軟體設計就是效能最佳化,如多級快取,特定場景的高效能庫存扣減

一個請求打到伺服器的基本鏈路:

DNS->閘道器->前端/後端

流量峰值也應該逐層減少:

1.1 系統隔離

秒殺活動因其高峰值特性,一般把它隔離出來成一個獨立的秒殺系統(常規服務都是按領域特性做縱切,但這裡按品類做橫切,帶有秒殺活動標的商品將會分流到獨立的秒殺系統叢集)。

考慮到交易系統體量很大,若為秒殺品類把整個交易系統複製一份,成本過大。所以把隔離區分為物理隔離和邏輯隔離:

  • 需定製化邏輯的能力和有特殊非功能性要求的能力剝離出來做物理隔離
  • 標準化且沒有特殊非功能要求的能力採用邏輯隔離

部署架構圖

先採用獨立的秒殺域名和nginx叢集(物理隔離)。這樣:

  • 可以隔離流量大頭,防止峰值衝擊交易系統(非功能性需求)

  • 靈活擴充套件,針對不同時段的流量預估擴充套件nginx及後邊服務的規模(非功能性需求)

  • 靈活增減私有的防控邏輯,而不影響原交易系統(定製化邏輯)

接著,將商詳頁和下單頁獨立部署(前端+BFF,物理隔離)。基於秒殺活動的玩法特徵,海量使用者在活動快開始時會反覆重新整理商詳頁,在活動開始時又會瞬時併發到訪問下單頁,所以這倆頁面都是承受流量衝擊的大頭,需隔離開(非功能性需求)。同時因秒殺活動特性,商品屬極端供不應求的場景,賣家佔優勢,所以可做服務降級,以降低計算資源消耗、提高效能(定製化邏輯)。如:

  • 商詳頁可將履約時效拿掉,不再計算預計多久能到貨。還可拿掉評價資訊,不再展示評價
  • 下單頁可不再計算優惠金額分攤,秒殺商品不參與任何疊加優惠活動。僅保留必要資訊,如商品資訊,商品主圖,購買按鈕,下單按鈕等
  • 結算頁、收銀臺,則看情況,若流量壓力不大,可不用物理隔離:
    • 支付扣減庫存下單的壓力會直接傳遞到結算
    • 但下單扣減就不需要併發支付,僅搶到的使用者需結算,壓力就很小
    • 為降低流量影響面積,這裡假設下單扣減,畢竟秒殺商品也不怕客戶下單後不支付

秒殺商品不怕客戶不支付的原因

  • 商品吸引力:秒殺商品往往是極具吸引力的折扣商品,搶購使用者通常都會完成支付,未支付的比例較低
  • 多次釋放機制:即使有使用者不支付,庫存會在短時間內釋放出來,其他使用者可以繼續搶購,這個過程在秒殺活動的激烈程度下通常很快完成。

最後,商品購買成功還需要依賴,訂單系統建立訂單,庫存系統扣減商品庫存,結算系統確認支付等等步驟。到達這裡,流量相對已較平穩,且邏輯上沒啥定製化訴求(壓力小,沒必要圍繞效能做定製化),所以採用邏輯隔離複用原交易系統的叢集。邏輯隔離兩種實現思路:

  • 依賴限流框架,如在訂單系統設定來源是秒殺系統BFF的建立訂單請求,TPS不超100,併發連線數不能超過20
  • 依賴RPC框架,RPC框架可設定分組,只把訂單系統叢集裡面部分服務節點設定成“秒殺組”,再把秒殺服務BFF的客戶端也設定為“秒殺組”,那麼秒殺系統的流量就只會打到訂單系統叢集裡面屬於“秒殺組”的節點上。這種隔離方式分割了叢集,叢集節點少了,出現故障發生過載的可能就提高了,可能會導致秒殺系統不可用

Q:為啥叢集節點少了,出現故障發生過載的可能就提高?

A:好比公里原本4條道能並行4輛車,現在給按車輛型別分成了機動車和公交車專用,機動車道2條。如果其中1條機動車道發生車禍,原本分散在2條道上的車流就要匯聚在1條道,原本順暢的通行可能立馬就開始堵車了。

1.2 多級快取

在系統的多個層級進行資料快取,以提高響應效率,高併發架構中最常用方案之一。

1.2.1 DNS層

一般將靜態資源掛到CDN上,藉助CDN來分流和提高響應效率。以秒殺系統為例,就是將秒殺前端系統的商詳頁和下單頁快取到CDN網路上。

藉助CDN的使用者請求鏈路:

如果使用者終端有頁面快取就走終端本地快取,沒有就請求遠端CDN的域名(靜態資源走CDN域名),請求來到DNS排程的節點,排程一個最近的CDN節點,如果該CDN節點有頁面快取則返回,沒有則向緣站發起溯源,請求就會走普通鏈路過秒殺系統ng到秒殺系統前端。

1.2.2 閘道器層

閘道器這個有多種組合情況,最簡單的就是一個接入層閘道器加一個應用層閘道器,比如:ISV(四層)-> Nginx(七層)。以這個為例,這裡的快取最佳化主要看接入層的負載均衡演算法和應用層的本地快取和集中記憶體快取。

快取還要提LB演算法,是因為節點的本地快取的有效性和LB演算法強繫結。常用LB演算法有輪詢(也叫取模)和一致性雜湊:

  • 輪詢可以讓請求分發更均衡,但同個快取key的請求不一定會路由到同個應用層Nginx上,Nginx的本地快取命中率低
  • 一致性雜湊可讓同個快取key路由到同個應用層Nginx上,Nginx的本地快取命中率高,但其請求分發不均衡容易出現單機熱點問題。有種做法是設定一個閾值,當單節點請求超過閾值時改為輪詢,可以算是自適應性負載均衡的變種。但這種基於閾值判斷的做法在應對真正的高併發時效果並不理想。

所以想運用本地快取強依賴業務運營,需對每個熱點商品key有較為準確的流量預估,並人為的組合這些商品key,進而控制流量均勻的落到每個應用層Nginx上(其實就是資料分片,然後每片資料流量一致)。這非常困難,所以筆者認為,還是採用輪詢加集中記憶體快取比較簡單有效。

從接入層開始帶有本地快取和集中記憶體快取的請求鏈路:

1.2.3 服務層

應用層ngnix->秒殺系統BFF->訂單服務

其實兩兩組合和閘道器層是一樣的場景。應用層ngnix基於ngnix的負載均衡轉發請求到秒殺系統BFF,秒殺系統BFF基於RPC框架的負載均衡轉發請求到訂單服務。都面臨著負載均衡策略選擇和是否啟用本地快取的問題。不一樣的點只是快取的粒度和啟用快取的技術棧選擇。

1.2.4 多級快取失效

因為快取分散到多層,很難用單一技術棧應對快取失效問題,但都等到快取過期,這種更新時延較長又不一定能被業務接受。

有個做法是基於DB的binlog監聽,各層監聽自己相關的binlog資訊,在發生快取被變更的情況時,及時讓整合記憶體的快取失效。

本地快取在這裡還有個缺陷,就是快取失效時需要廣播到所有節點,讓每個節點都失效,對於頻繁變更的熱key就可能產生訊息風暴。

1.3 無損消峰

為流量峰值準備對應的服務叢集,首先成本太高,接著單純的水平擴充套件也不一定能做到(分散式架構存在量變引起質變的問題,資源擴充套件到一定量級,原先的技術方案整個就不適用了。

如當叢集節點太多,服務註冊發現可能會有訊息風暴;出入口的頻寬出現瓶頸,需要在部署上分流)。更別說這個峰值也不受控制,想要高枕無憂就會有很高的冗餘浪費。

所以一般採用消峰:

  • 直接斷頭,把超出負荷的流量直接都丟棄掉,也就是我們常見的限流,也稱為有損消峰(如果這是大促的訂單,砍掉的可能都是錢,這個有損是真的資損)
  • 分流,也叫消峰填谷,透過技術或者業務手段將請求錯開,鋪到更長的時間線上,從而降低峰值,常見的有MQ非同步消費和驗證碼問答題。先談無損消峰

1.3.1 MQ非同步消費

MQ依賴三個特性可以做到平滑的最終一致,分別是:

  • 有訊息堆積才能起到蓄水池的效果,在出水口流速恆定的情況下能接住入水口瞬時的大流量
  • 有勻速消費才能讓下游叢集的流量壓力恆定,不會被衝擊
  • 有至少成功一次,才能保證事物最終一致

以秒殺系統BFF下單操作向訂單服務建立訂單為例。如果沒有訊息佇列(MQ),同時有100W個建立請求,訂單系統就必須承擔100W個並行連線的壓力。但是,如果使用了MQ,那麼100W個建立請求的壓力將全部轉移到MQ服務端,訂單系統只需要維持64個並行連線,以穩定地消費MQ服務端的訊息。

這樣一來,訂單系統的叢集規模就可以大大減小,而且更重要的是,系統的穩定性得到了保障。由於並行連線數的減少,資源競爭也會降低,整體響應效率也會提高,就像在食堂排隊打飯一樣,有序排隊比亂搶效率更高。但是,使用者體驗可能會受到影響,因為點選搶購後可能會收到排隊提示(其實就是友好提示),需要延遲幾十秒甚至幾分鐘才能收到搶購結果。

1.3.2 驗證碼問答題

兩層好處:

  • 消峰,使用者0.5秒內併發的下單事件,因為個人的手速差異,被平滑的分散到幾秒甚至幾十秒中
  • 防刷,提高機器作弊成本
驗證碼

基本實現步驟:

  • 請求到來時生成1串6位隨機字串 verification_code

  • 用特定字首拼接使用者ID作為key,verification_code做為value存redis,超時5s

  • 生成一個圖片,將 verification_code 寫到圖片上,返回給使用者

  • 使用者輸入圖片中字串

  • 從redis裡面取出 verification_code 做比對,如果一致,執行下單操作

但這樣其實是可以用暴力破解的,比如,用機器仿照一個使用者發起10W個請求攜帶不同的6位隨機字元。所以校驗驗證碼時可以使用 GETDEL ,讓驗證碼校驗無論對錯都讓驗證碼失效。

問答題

基本實現思路和驗證碼幾乎一樣。差別在於,問答題的題庫要提前生成,請求到來時從題庫中拿到一組問題和答案。然後把答案存redis,問題塞到圖片裡返回給使用者。

驗證碼和問答題具有很好的消峰效果。特別是問答題,想要提高消峰效果只要提高問題難度就行,例如,筆者曾經在12306上連續錯了十幾次問答題。但是這也是使用者體驗有損的,例如,雖然筆者當初未能成功搶到票而感到沮喪,但這魔性的題庫依然把筆者成功逗笑。

無損消峰,無損了流量,但損失了使用者體驗。現如今技術水平在不斷進步,解決方法在增多,這些有損使用者體驗的技術方案可能都會慢慢退出歷史舞臺,就像淘寶取消618預售。
關注我,緊跟本系列專欄文章,咱們下篇再續!

作者簡介:魔都架構師,多家大廠後端一線研發經驗,在分散式系統設計、資料平臺架構和AI應用開發等領域都有豐富實踐經驗。

各大技術社群頭部專家博主。具有豐富的引領團隊經驗,深厚業務架構和解決方案的積累。

負責:

  • 中央/分銷預訂系統效能最佳化
  • 活動&券等營銷中臺建設
  • 交易平臺及資料中臺等架構和開發設計
  • 車聯網核心平臺-物聯網連線平臺、大資料平臺架構設計及最佳化
  • LLM Agent應用開發
  • 區塊鏈應用開發
  • 大資料開發挖掘經驗
  • 推薦系統專案

目前主攻市級軟體專案設計、構建服務全社會的應用系統。
本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章