愛伊米

B 站高可用架構實踐

【導讀】本文整理了 B 站在雲+社群沙龍分享的高可用架構,一起來學習小破站的穩定性實踐吧!

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

負載均衡

B 站高可用架構實踐

BFE 就是指邊緣節點,BFE 選擇下游 IDC 的邏輯權衡:

離 BFE 節點比較近的

基於頻寬的排程策略

某個 IDC 的流量已經過載,選擇另外一個 IDC

B 站高可用架構實踐

當流量走到某個 IDC 時,這個流量應該如何進行負載均衡?

B 站高可用架構實踐

問題:RPC 定時傳送的 ping-pong,也即 healthcheck,佔用資源也非常多。服務 A 需要與賬號服務維持長連線傳送 ping-pong,服務 B 也需要維持長連線傳送 ping-pong。這個服務越底層,一般依賴和引用這個服務的資源就越多,一旦有任何抖動,那麼產生的這個故障面是很大的。那麼應該如何解決?

B 站高可用架構實踐

解決:以前是一個 client 跟所有的 backend 建立連線,做負載均衡。現在引入一個新的演算法,

子集選擇演算法

,一個 client 跟一小部分的 backend 建立連線。圖片中示例的演算法,是從《Site Reliability Engineering》這本書裡看的。

如何規避單叢集抖動帶來的問題?多叢集。

B 站高可用架構實踐

如上述圖片所示,如果採用的是 JSQ 負載均衡演算法,那麼對於 LBA 它一定是選擇 Server Y 這個節點。但如果站在全域性的視角來看,就肯定不會選擇 Server Y 了,因此這個演算法缺乏一個全域性的視角。

如果微服務採用的是 Java 語言開發,當它處於 GC 或者 FullGC 的時候,這個時候發一個請求過去,那麼它的 latency 肯定會變得非常高,可能會產生過載。

新啟動的節點,JVM 會做 JIT,每次新啟動都會抖動一波,那麼就需要考慮如何對這個節點做預熱?

B 站高可用架構實踐

如上圖所示,採用 “the choice-of-2” 演算法後,各個機器的 CPU 負載趨向於收斂,即各個機器的 CPU 負載都差不多。Client 如何拿到後臺的 Backend 的各項負載?是採用 Middleware 從 Rpc 的 Response 裡面獲取的,有很多 RPC 也支援獲取元資料資訊等。

還有就是 JVM 在啟動的時候做 JIT,以前的預熱做法:手動觸發預熱程式碼,然後再引入流量,再進行服務發現註冊等,不是非常通用。透過改進負載均衡演算法,引入懲罰值的方式,慢慢放入流量進行預熱。

限流

B 站高可用架構實踐

用 QPS 限制的陷阱:

不同的引數,請求的資料量是不同的,對一個程序的一個吞吐是有影響的。

業務是經常迭代的,配一個靜態的閾值,這個非常困難。能否按照每一個服務用多少個 CPU 來做限流?

每一個 API 都是有重要性的:非常重要、次重要,這樣配置限流、做過載保護的時候,可以使用不同的閾值。

每個服務都要配一個限流,是非常煩人的,需要壓測,是不是可以

自適應去限流

B 站高可用架構實踐

每個 Client 如何知道自己這一次需要申請多少 Quota ?基於歷史資料視窗的 QPS。

節點與節點之間是有差異的,分配演算法不夠好,會導致某些節點產生飢餓。那麼可以採用

最大最小公平演算法

,儘可能地比較公平地去分配資源,來解決這個問題。

B 站高可用架構實踐

當量再大一點的時候,如果 Backend 一直忙著拒絕請求,比如傳送 503,那麼它還是會掛掉。這種情況就要考慮從 Client 去截流。此處,又提到了 Google 《Site Reliability Engineering》這本書裡面的一個演算法,即 Client 是按照

一定機率

去截流。那麼這個機率怎麼計算?一個是總請求量:,一個是成功的請求量:。如果服務報錯率比較高,意味著 不怎麼增長, 一直增長,最終這個公式求極限,它會等於 1,所以它的丟棄機率是非常高的。基於這麼一個簡單的公式,不需要依賴什麼 ZooKeeper,什麼協調器之類的,就可以得到一個機率丟棄一些請求。它儘可能的在服務不掛掉的情況下,放更多的流量進去,而不是像 Netflix 一樣全部拒掉。

B 站高可用架構實踐

連鎖故障通常都是某一個節點過載了掛掉,流量又會去剩下的 n - 1 個節點,又扛不住,又掛掉,所以最終一個一個挨著雪崩。所以過載保護的目的是為了自保。

B 站參考了阿里的 Sentinel 框架、Netflix 的一些文章等,最終採用的是類似於 TCP BBR 探測的思路和演算法。簡單說:當 CPU 達到 80% 的時候,這個時候我們認為流量過載,如果此時吞吐量比如 100,用它作為閾值,瞬時值的請求比如是 110,那就可以丟掉 10 個流量。這樣就可以實現一個限流演算法。

B 站高可用架構實踐

CPU 抖來抖去,使用 CPU 滑動均值(綠色線)可以跳動的沒有這麼厲害。這個 CPU 針對不同介面的優先順序,例如低優先順序 80% 觸發,高優先順序 90% 觸發,可以定為一個閾值。

那麼吞吐如何計算?

利特爾法則

。當前的 QPS * 延遲 = 吞吐,可以用過去的一個視窗作為指標。一旦丟棄流量,CPU 立馬下來,演算法抖動非常厲害。圖二右側黃色線表示抖動非常高,綠色線表示放行的流量也是抖動非常高,所以又加了冷卻時間,比如持續幾秒鐘,再重新判斷。

重試

B 站高可用架構實踐

BFE: 動態 CDN

SLB: LVS + Nginx 實現,四七層負載均衡

BFF: 業務邏輯組裝、編排

問題:每一層都重試,這一層 3 次,那一層 3 次,會指數級的放大。解決:只在失敗這一層重試,如果重試之後失敗,請返回一個全域性約定好的錯誤碼,比如說:過載,無需重試,發現這個錯誤碼,通通放行,避免級聯重試。

重試都應該無腦的重試三次嗎?API 級別的

重試需要考慮叢集的過載情況

。是不是可以約定一個重試比例呢?比如只允許 10% 的流量進行重試,Client 端做統計,當發現有 10% 都是重試,那麼剩下的都拒絕掉。這樣最多產生 1。1 倍的放大,重試 3 次,極端情況下,會產生 3 倍放大。還有在重試的時候,儘量引入隨機、指數遞增的一個重試周期,大家不要都重試 1 秒鐘,有可能會堆砌一個

重試的波峰

重試的統計圖和記錄 QPS 的圖分開。問題診斷的時候,可以知道它是來自流量重試導致的問題放大。

B 站高可用架構實踐

某個服務不可用的時候,使用者總是會猛點,那麼這個時候,需要去限制它的頻次,一個短週期內不允許發重複請求。這種策略,有可能會根據不同的過載情況經常調這種策略,那麼可以掛載到每一個 API 裡面。

超時

B 站高可用架構實踐

大部分的故障都是因為超時控制不合理導致的。

某個高延遲服務可能會導致 Client 堆積,Client 執行緒會阻塞,上游流量不斷進來,下游的消費速度跟不上上游的流入速度,程序會堆積越來越多請求,可能會 OOM。

超時的策略本質是就是為了

丟棄

或者

消耗掉

請求。

下游 2 秒返回,上游配置了 1 秒,上游超時已經返回給使用者,下游還在執行,浪費資源。

某個服務需要在 1 秒返回,內部可能需要訪問 Redis,需要訪問 RPC,需要訪問資料庫,時間加起來就超過 1 秒,那麼訪問完每一層,應該計算供下一層使用的超時時間還剩多少可用。在 go 語言裡,可能會使用 Context,每一個網路請求開始的階段,都要根據配置檔案配置的超時時間,和當前剩餘多少,取一個最小值,最終整個超時時間不會超過 1 秒。

B 站高可用架構實踐

透過 RPC 的元資料傳遞,類似 HTTP 的 request header,帶給其它服務。例如在圖中,就是把 700ms 這個配額傳遞給 Service B。

下游服務作為服務提供者,在他的 RPC。IDL 檔案中把自己的超時要配上,那麼用 IDL 檔案的時候,就知道是 200 ms,不用去問。

應對連鎖故障

優雅降級:一開始千人千面,後來只返回熱門的

QA

Q: 請問負載均衡依據的 metric 是什麼?

A: 服務端主要用 CPU,客戶端用的是

健康度

,指連線的成功率,延遲也很重要,每個 Client 往不同的 Backend 發了多少個請求,四個指標歸一,寫一個線性方程,進行打分。

Q: BFE 到 SLB 走公網還是專線?

A: 既有公網,又有專線。

Q: Client 幾千量級,每 10 秒 ping-pong 一下,會不會造成蠻高的 CPU?

A: 如果 Backend 很多的話,那麼這個的確會造成。

Q: 多叢集切換是否有阻塞的點?

A: 一個 Client 連線到各個叢集,subset 演算法,每個叢集都有 Cache

Q: 負載均衡的探針是怎麼做的?

A: 懲罰值,比如 5 秒,慢慢放流量

Q: Quota-Server 限流有開源實現嗎?

A: 目前看到的都是針對單節點的。

Q: 客戶端統計是否有點太多?

A: 可以做到 Sidecar、Service Mesh 裡面

Q: 超時傳遞是不是太嚴格?

A: 有些情況下即便超時也要執行,可以透過 RPC Context 管控

Q: 每個 RPC 都獲取 CPU 會不會很昂貴?

A: 後臺開啟執行緒定時計算 CPU 平滑均值

Q: 線上壓測和測試環境壓測 CPU 不一致

A: RPC 路由加影子庫

Q: CC 攻擊

A: 邊緣節點或者核心機房都有防止 CC 攻擊的一些手段,只要不是分散式搞你,都能找到流量特徵進行管控

轉自:

kunzhao。org/docs/cloud-plus-bbs/bilibili-high-availability/