月均活躍用戶達1.3億,B站高可用架構實踐

流量洪峯下要做好高服務質量的架構是一件具備挑戰的事情,本文詳細闡述了從 Google SRE 的系統方法論以及實際業務的應對過程中出發,一些體系化的可用性設計。對我們瞭解系統的全貌、上下游的聯防有更進一步的幫助。

月均活躍用戶達1.3億,B站高可用架構實踐

本文來自公衆號雲加社區(ID:QcloudCommunity)

負載均衡

負載均衡具體分成兩個方向,一個是前端負載均衡,另一個是數據中心內部的負載均衡。

月均活躍用戶達1.3億,B站高可用架構實踐

前端負載均衡方面,一般而言用戶流量訪問層面主要依據 DNS,希望做到最小化用戶請求延遲。

將用戶流量最優地分佈在多個網絡鏈路上、多個數據中心、多臺服務器上,通過動態 CDN 的方案達到最小延遲。

以上圖爲例,用戶流量會先流入 BFE 的前端接入層,第一層的 BFE 實際上起到一個路由的作用,儘可能選擇跟接入節點比較近的一個機房,用來加速用戶請求。

然後通過 API 網關轉發到下游的服務層,可能是內部的一些微服務或者業務的聚合層等,最終構成一個完整的流量模式。

基於此,前端服務器的負載均衡主要考慮幾個邏輯:

  • 儘量選擇最近節點
  • 基於帶寬策略調度選擇 API 進入機房
  • 基於可用服務容量平衡流量

月均活躍用戶達1.3億,B站高可用架構實踐

數據中心內部的負載均衡方面,理想情況下會像上圖右邊顯示那樣,最忙和最不忙的節點所消耗的 CPU 相差幅度較小。

但如果負載均衡沒做好,情況可能就像上圖左邊一樣相差甚遠。由此可能導致資源調度、編排的困難,無法合理分配容器資源。

因此,數據中心內部負載均衡主要考慮:

  • 均衡流量分發
  • 可靠識別異常節點
  • scale-out,增加同質節點以擴容
  • 減少錯誤,提高可用性

月均活躍用戶達1.3億,B站高可用架構實踐

我們此前通過同質節點來擴容就發現,內網服務出現 CPU 佔用率過高的異常,通過排查發現背後 RPC 點到點通信間的 Health Check 成本過高,產生了一些問題。

另外一方面,底層的服務如果只有單套集羣,當出現抖動的時候故障面會比較大,因此需要引入多集羣來解決問題。

通過實現 Client 到 Backend 的子集連接,我們做到了將後端平均分配給客戶端,同時可以處理節點變更,持續不斷均衡連接,避免大幅變動。

多集羣下,則需要考慮集羣遷移的運維成本,同時集羣之間業務的數據存在較小的交集。

月均活躍用戶達1.3億,B站高可用架構實踐

回到 CPU 忙時、閒時佔用率過大的問題,我們會發現這背後跟負載均衡算法有關。

第一個問題:對於每一個 QPS,實際上就是每一個 query、查詢、API 請求,它們的成本是不同的。

節點與節點之間差異非常大,即便你做了均衡的流量分發,但是從負載的角度來看,實際上還是不均勻的。

第二個問題:存在物理機環境上的差異。因爲我們通常都是分年採購服務器,新買的服務器通常主頻 CPU 會更強一些,所以服務器本質上很難做到強同質。

月均活躍用戶達1.3億,B站高可用架構實踐

基於此,參考 JSQ(最閒輪訓)負載均衡算法帶來的問題,發現缺乏的是服務端全局視圖,因此我們的目標需要綜合考慮負載和可用性。

我們參考了《The power of two choices in randomized load balancing》的思路,使用 the choice-of-2 算法,隨機選取的兩個節點進行打分,選擇更優的節點:

  • 選擇 Backend:CPU,client:health、inflight、latency 作爲指標,使用一個簡單的線性方程進行打分。
  • 對新啓動的節點使用常量懲罰值(penalty),以及使用探針方式最小化放量,進行預熱。
  • 打分比較低的節點,避免進入“永久黑名單”而無法恢復,使用統計衰減的方式,讓節點指標逐漸恢復到初始狀態(即默認值)。

通過優化負載均衡算法以後,我們做到了比較好的收益。

限流

避免過載,是負載均衡的一個重要目標。隨着壓力增加,無論負載均衡策略如何高效,系統某個部分總會過載。

我們優先考慮優雅降級,返回低質量的結果,提供有損服務。在最差的情況,妥善的限流來保證服務本身穩定。

月均活躍用戶達1.3億,B站高可用架構實踐

 

限流這塊,我們認爲主要關注以下幾點:

  • 針對 QPS 的限制,帶來請求成本不同、靜態閾值難以配置的問題。
  • 根據 API 的重要性,按照優先級丟棄。
  • 給每個用戶設置限制,全局過載發生時候,針對某些“異常”進行控制非常關鍵。
  • 拒絕請求也需要成本。
  • 每個服務都配置限流帶來的運維成本。

月均活躍用戶達1.3億,B站高可用架構實踐

在限流策略上,我們首先採用的是分佈式限流。我們通過實現一個 quota-server,用於給 Backend 針對每個 Client 進行控制,即 Backend 需要請求 quota-server 獲取 Quota。

這樣做的好處是減少請求 Server 的頻次,獲取完以後直接本地消費。算法層面使用最大最小公平算法,解決某個大消耗者導致的飢餓。

月均活躍用戶達1.3億,B站高可用架構實踐

在客戶端側,當出現某個用戶超過資源配額時,後端任務會快速拒絕請求,返回“配額不足”的錯誤。

有可能後端忙着不停發送拒絕請求,導致過載和依賴的資源出現大量錯誤,處於對下游的保護兩種狀況,我們選擇在 Client 側直接進行流量,而不發送到網絡層。

我們在 Google SRE 裏學到了一個有意思的公式,max(0, (requests- K*accepts) / (requests + 1))。

通過這種公式,我們可以讓 Client 直接發送請求,一旦超過限制,按照概率進行截流。

月均活躍用戶達1.3億,B站高可用架構實踐

在過載保護方面,核心思路就是在服務過載時,丟棄一定的流量,保證系統臨近過載時的峯值流量,以求自保護。

常見的做法有基於 CPU、內存使用量來進行流量丟棄;使用隊列進行管理;可控延遲算法:CoDel 等。

簡單來說,當我們的 CPU 達到 80% 的時候,這個時候可以認爲它接近過載,如果這個時候的吞吐達到 100,瞬時值的請求是 110,我就可以丟掉這 10 個流量。

這種情況下服務就可以進行自保護,我們基於這樣的思路最終實現了一個過載保護的算法。

月均活躍用戶達1.3億,B站高可用架構實踐

我們使用 CPU 的滑動均值(CPU>800 )作爲啓發閾值,一旦觸發就進入到過載保護階段。

算法爲:(MaxPass*AvgRT)<InFlight

限流效果生效後,CPU 會在臨界值(800)附近抖動,如果不使用冷卻時間,那麼一個短時間的 CPU 下降就可能導致大量請求被放行,嚴重時會打滿 CPU。

在冷卻時間後,重新判斷閾值(CPU>800 ),是否持續進入過載保護。

重試

月均活躍用戶達1.3億,B站高可用架構實踐

流量的走向,一般會從 BFE 到 LB(負載均衡)然後經過 API 網關再到 BFF、微服務最後到數據庫,這個過程要經過非常多層。

在我們的日常工作中,當請求返回錯誤,對於 Backend 部分節點過載的情況下,我們應該怎麼做?

  • 首先我們需要限制重試的次數,以及基於重試分佈的策略。
  • 其次,我們只應該在失敗層進行重試,當重試仍然失敗時,我們需要全局約定錯誤碼,避免級聯重試。
  • 此外,我們需要使用隨機化、指數型遞增的充實週期,這裏可以參考 Exponential Backoff 和 Jitter。
  • 最後,我們需要設定重試速率指標,用於診斷故障。

月均活躍用戶達1.3億,B站高可用架構實踐

而在客戶端側,則需要做限速。因爲用戶總是會頻繁嘗試去訪問一個不可達的服務,因此客戶端需要限制請求頻次,可以通過接口級別的 error_details,掛載到每個 API 返回的響應裏。

超時

月均活躍用戶達1.3億,B站高可用架構實踐

我們之前講過,大部分的故障都是因爲超時控制不合理導致的。首當其衝的是高併發下的高延遲服務,導致 Client 堆積,引發線程阻塞,此時上游流量不斷涌入,最終引發故障。

所以,從本質上理解超時它實際就是一種 Fail Fast 的策略,就是讓我們的請求儘可能消耗,類似這種堆積的請求基本上就是丟棄掉或者消耗掉。

另一個方面,當上遊超時已經返回給用戶後,下游可能還在執行,這就會引發資源浪費的問題。

再一個問題,當我們對下游服務進行調優時,到底如何配置超時,默認值策略應該如何設定?

生產環境下經常會遇到手抖或者錯誤配置導致配置失敗、出現故障的問題。所以我們最好是在框架層面做一些防禦性的編程,讓它儘可能讓取在一個合理的區間內。

進程內的超時控制,關鍵要看一個請求在每個階段(網絡請求)開始前,檢查是否還有足夠的剩餘來處理請求。

另外,在進程內可能會有一些邏輯計算,我們通常認爲這種時間比較少,所以一般不做控制。

月均活躍用戶達1.3億,B站高可用架構實踐

現在很多 RPC 框架都在做跨進程超時控制,爲什麼要做這個?跨進程超時控制同樣可以參考進程內的超時控制思路,通過 RPC 的源數據傳遞,把它帶到下游服務,然後利用配額繼續傳遞,最終使得上下游鏈路不超過一秒。

應對連鎖故障

結合我們上面講到的四個方面,應對連鎖故障,我們有以下幾大關鍵點需要考慮。

月均活躍用戶達1.3億,B站高可用架構實踐

第一,我們需要儘可能避免過載。因爲節點一個接一個掛了的話,最終服務會雪崩,有可能機羣都會跟着宕掉,所以我們才提到要做自保護。

第二,我們通過一些手段去做限流。它可以讓某一個 Client 對服務出現高流量併發請求時進行管控,這樣的話服務也不容易死。

另外,當我們無法正常服務的時候,還可以做有損服務,犧牲掉一些非核心服務去保證關鍵服務,做到優雅降級。

第三,在重試策略上,在微服務內儘可能做退避,儘可能要考慮到重試放大的流量倍數對下游的衝擊。

另外還要考慮在移動端用戶用不了某個功能的情況下,通常會頻繁刷新頁面,這樣產生的流量衝擊,我們在移動端也要進行配合來做流控。

第四,超時控制強調兩個點,進程內的超時和跨進程的傳遞。最終它的超時鏈路是由最上層的一個節點決定的,只要這一點做到了,我覺得大概率是不太可能出現連鎖故障的。

第五,變更管理。我們通常情況下發布都是因爲一些變更導致的,所以說我們在變更管理上還是要加強,變更流程中出現的破壞性行爲應該要進行懲罰,儘管是對事不對人,但是還是要進行懲罰以引起重視。

第六,極限壓測和故障演練。在做壓測的時候,可能壓到報錯就停了。我建議最好是在報錯的情況下,仍然要繼續加壓,看你的服務到底是一個什麼表現?它能不能在過載的情況下提供服務?

在上了過載保護算法以後,繼續加壓,積極拒絕,然後結合熔斷的話,可以產生一個立體的保護效果。

經常做故障演練可以產生一個品控手冊,每個人都可以學習,經常演練不容易慌亂,當在生產環境中真的出現問題時也可以快速投入解決。

第七,考慮擴容、重啓、消除有害流量。

月均活躍用戶達1.3億,B站高可用架構實踐

如上圖所示的參考,就是對以上幾個策略的經典補充,也是解決各種服務問題的玄學。

 

作者:毛劍

簡介:bilibili 技術總監,騰訊雲最具價值專家(TVP)。負責 bilibili 數據平臺部,擁有近十年的服務端研發經驗。擅長高性能、高可用的服務端研發,熟悉 Go、Java、C 等語言。在 B 站參與了,從巨石架構到微服務的完整轉型,包含微服務治理、微服務可用性設計,微服務數據一致性設計,微服務中間件,微服務監控,微服務日誌收集,微服務負載均衡,和微服務 RPC 框架開發等。開源業內比較有影響力的項目:https://github.com/Terry-Mao/goim,分佈式 IM 長連接廣播服務;https://github.com/Terry-Mao/bfs,分佈式小文件存儲。

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