龐大用戶量,每秒數億請求,微信服務器如何做到不“失控”?

微信作為當之無愧的國民級應用,系統複雜程度超乎想象:其後臺由三千多個移動服務構成,每天需處理大約十的10~11次方個外部請求,整體需要每秒處理大約幾億個請求!那麼微信究竟是如何保證系統穩定性並有序處理各類請求的?本文的作者從技術上深入解讀了《用於擴展微信微服務的過載控制》一文,介紹了已在微信上運行了五年之久的過載控制系統DAGOR。

龐大用戶量,每秒數億請求,微信服務器如何做到不“失控”?

以下為譯文:

最近我讀了一篇論文《Overload control for scaling WeChat microservices(用於擴展微信微服務的過載控制)》,十分喜歡。

這篇論文主要分兩部分:首先,我們能瞭解一些微信後臺的內部消息;其次,作者分享了一種經過實踐檢驗的過載控制系統DAGOR,該系統已經在微信上運行了五年。友情提示,這個系統在設計時就考慮了微服務架構的特殊情況,所以如果你想在自己的微服務上採用某種策略,那最好還是以這個系統為起點參考。

現在的微信後臺由3000多個移動服務構成,包括實時消息、社交網絡、移動支付和第三方認證等,平臺每天能處理大約1010~1011外部請求。每個請求可能觸發更多的內部請求,所以微信的後臺作為一個整體,需要每秒處理大約幾億個請求。

“微信的微服務包含3000多個服務,運行在微信業務系統中的20000多臺機器上,隨著微信越來越流行,這個數字還在不斷增加……由於微信一直在不斷髮展,它的微服務系統也在迅速更新。例如,從2018年3月到5月間,微信的微服務平均每天大約有1000個修改。”

微信將微服務分類為“入口層”服務(接收外部請求的前端服務)、“共享層”服務(中間層協調服務)和“基本服務”(不調用其他服務的服務,請求在這裡終結)。

龐大用戶量,每秒數億請求,微信服務器如何做到不“失控”?

每天,峰值請求大約是日平均請求的三倍。在每一年的固定時期(例如在中國的新年前後),峰值負載可能達到日平均負載的10倍。

2.基於微服務的大型平臺過載控制的難點

“過載控制……對於在任何不可預測的負載高峰下都要保證24x7服務大型在線應用來說極其重要。”

傳統的過載控制機制主要用於只有少量服務組件的情況,通常包括一個較窄的“前門”,加上一些不重要的依賴。

“……現代的在線服務的架構和依賴變得越來越複雜,遠遠超過了傳統過載控制的設計考慮。”

  • 發送到微信後臺的請求沒有單一的入口點,而傳統方式是在全局入口點(網關)上以中心化的負載監視為主,因此無法使用。
  • 特定請求的服務調用圖可能依賴於請求自身的數據和服務參數,甚至同一種請求的調用圖都可能不同。所以,當特定服務出現過載時,很難確定應該拒絕哪一種請求來緩解壓力。
  • 過度的請求拒絕(特別是在調用圖深處或者請求處理後期拒絕)會浪費大量計算資源,而且會由於高延遲而影響用戶體驗。
  • 由於服務的調用圖非常複雜,而且在不斷變化,有效的跨服務協調的維護成本和系統額外開銷非常高。

由於一個服務可能向它依賴的服務發出多個請求,還可能給多個後臺服務發出請求,所以在處理過載控制時必須謹慎。作者採用了“序列過載”來描述多於一個過載服務被調用的情況,或一個過載服務被調用多次的情況。

“序列過載給有效的過載控制帶來了挑戰。直覺上,當服務過載時隨機進行load shedding能將系統的吞吐量維持在飽和狀態。但是,序列過載可能會導致吞吐量出現預期之外的下降……”

考慮一個簡單的情況,服務A調用服務B兩次。如果B拒絕掉一半的請求,那麼A的成功率就會下降到0.25。

微信的過載控制系統叫做DAGOR,它的目標是給所有服務提供過載控制,因此被設計成與服務無關。過載控制運行在單個服務器的粒度上,因為中心化的全局協調太昂貴了。但是,它能夠與輕量級的服務器間合作協議配合使用,後者在處理序列過載的情況時是必須的。最後,DAGOR應當在load shedding不可避免時儘可能維持服務的成功率。消耗在失敗的任務上的計算資源(如CPU、I/O等)應當保持最小。

我們要解決兩個最基本的任務:檢測過載狀況,決定檢測到過載後的對策。

過載檢測

對於過載檢測,DAGOR採用了等待隊列請求平均等待時間(即排隊時間)。排隊時間可以有效地避免調用圖中的延遲造成的影響(可以與其他指標比較一下,比如請求處理時間等)。即使過載沒有發生,本地服務器上的服務處理時間也可能增加。DAGOR基於窗口進行監視,窗口大小為1秒或2000個請求,先到者為準。顯然,微信做得不錯:

“對於過載檢測,考慮到微信中的每個服務任務的默認超時時間為500毫秒,我們將請求平均排隊時間設置為20毫秒,以此作為服務器過載的標誌。這種經驗性的配置已經在微信的業務系統中使用了五年以上,其有效性已經被微信業務活動的健壯程度證明。”

任務控制

檢測到過載後,我們要決定怎樣處理過載。或者更直接地說,要確定拒絕哪些請求。首先我們注意到並非所有請求都是平等的:

“操作日誌表明,微信支付和即時消息的服務失敗時間是相似的,但微信支付上的用戶投訴要比即時消息高出100倍。”

為了以服務無關的方式來處理,每個請求在進入系統時都要指定業務優先級,這個優先級會流向下游的所有請求。用戶請求的業務優先級由被請求的操作類型決定。儘管有幾百個入口點,但只有幾十個入口點有明示的優先級,其他的均為默認優先級(較低的優先級)。優先級用一個有副本的哈希表來維護。

龐大用戶量,每秒數億請求,微信服務器如何做到不“失控”?

圖2 哈希表存儲了微信的各個入口服務進行操作的業務優先級

當過載控制被設置為業務優先級n時,所有n+1以上的請求都會被拒絕。對於混合型的負載,這種策略非常好用。但如果出現大量支付請求,所有請求的優先級都相同(均為p),那麼系統就會過載,於是就會把過載閾值設置為p-1,從而使負載降低。一旦檢測到負載降低,就會把過載閾值再設置為p,從而又導致過載。為了避免這種由大量優先級相同的請求導致的反覆情況,我們需要比業務優先級更細粒度的控制。

微信的解決方案很巧妙,它根據用戶ID增加了另一層控制。

“用戶優先級是由整個服務根據一個哈希函數動態生成的,該哈希函數接收用戶ID作為參數。每個入口服務都會在每個小時改變其哈希函數。這樣做的結果就是,來自同一個用戶的請求在一小時之內會被指定為同樣的優先級,但不同的小時會產生不同的用戶優先級。”

這樣能保證公平,同時為每個用戶在相對長的一段時間內提供一致的用戶體驗。這樣做還能幫助控制序列過載問題,因為來自同一個用戶優先級更高的請求高更容易在整個調用圖中暢通無阻。

將業務優先級和用戶優先級結合使用,能夠在每個業務優先級內提供128級的用戶優先級。

龐大用戶量,每秒數億請求,微信服務器如何做到不“失控”?

圖3 組合優先級

“每個業務優先級的每個層次都有128個用戶優先級,這樣得到的組合優先級就有幾萬個。組合優先級的調整是在用戶粒度上進行的。”

為什麼一定要使用用戶ID而不能使用會話ID呢?因為後者會讓用戶養成在服務差的時候重新登錄的習慣,進而會導致在原來的過載問題上出現大量的登錄!

DAGOR在每個服務器上維持一個直方圖,來跟蹤各個優先級的請求的大致分佈情況。當在某個窗口內檢測到過載時,DAGOR移動到第一個桶,將預期的負載減小5%。如果沒有負載,就會到第一個桶內,將預期負載增加1%。

服務器還會順便將每個響應消息的當前優先級發送到上游服務器。這樣,上游服務器就能知道當前下游服務的控制設置,從而甚至可以在發送請求之前進行本地控制。

因此,DAGOR過載控制系統的端到端架構如下所示:

龐大用戶量,每秒數億請求,微信服務器如何做到不“失控”?

圖4 DAGOR過載控制系統的工作流

實驗

DAGOR設計的最佳實證就是它已經在微信的產品環境裡運行了五年之久。但對於學術論文來說這還不夠,因此作者還提供了一系列模擬實驗。下面的幾張圖顯示了基於排隊時間的過載控制要比基於響應時間的好得多。優勢主要出現在系列過載的情況下(圖(b))。

龐大用戶量,每秒數億請求,微信服務器如何做到不“失控”?

圖6 利用不同的負載評測指標進行過載檢測:排隊時間 vs. 響應時間

與CoDel、SEDA相比,在系列過載的情況下,發送一個系列調用時DAGOR的請求成功率要高出50%。系列請求的數目越高,優勢就越大。

龐大用戶量,每秒數億請求,微信服務器如何做到不“失控”?

圖5 不同類型負載下的過載控制

最後為公平起見,可以看出CoDel更適合扇出較小的服務,而DAGOR能在多種不同請求的情況下達到同樣的成功率。

龐大用戶量,每秒數億請求,微信服務器如何做到不“失控”?

圖6 過載控制的公平比較

4.給開發者的三條建議

即使你不會完全按照該論文描述的方式使用DAGOR,作者也給你提了三條非常有價值的建議:

  • 大規模微服務架構下的過載控制必須是去中心化,每個服務必須是自動化;
  • 過載控制應該考慮各種反饋機制(例如DAGOR的協同控制),不要依賴於單一的開環啟發;
  • 過載控制設計應當根據實際負載的處理行為進行。


分享到:


相關文章: