天天看點

月均活躍使用者達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,分布式小檔案存儲。

繼續閱讀