0.概要
微服務架構中,服務數量增加,整體系統可用性會存在潛在問題,因此,需要一些額外的措施。
具體幾個方面:
- 問題:分佈式系統架構中,存在的問題
- 解決方法:上述問題的解決辦法?
- 注意事項
- HyStrix 框架的原理
特別說明:
Hystrix 已經進入「維護狀態」,現在 Netflix 已經啓用「resilience4j」框架,作爲替代方案。
1.問題
分佈式系統,隨着業務複雜度提高,系統不斷拆分,一個面向 C 端的 API 調用,其內部的 RPC 調用層層嵌套,調用鏈變長,會造成下述 2 個問題:
- API 接口可用性降低:
- 假設一次 api 請求,內部涉及 30 次 rpc 調用
- 每個微服務可用性 99.99%
- 則,api 請求的可用性爲 99.99% 的 30 次方 = 99.7% ,即,0.3% 的失敗率
- 系統阻塞,拒絕請求接入:
- 假設一次 api 請求,內部涉及 10 次 rpc 調用
- 只要 10 次 rpc 中,有一次請求超時,則,整個 api 調用就超時了
- 如果大量請求突發訪問,則,大量的線程都阻塞(block 等待超時)在這一服務上,新的請求無法接入
2.解決方法
爲了解決上述「API 接口可用性降低」和「系統阻塞、拒絕請求接入」問題,可以採用下述 4 中方法:
- 熔斷:服務熔斷,一旦觸發「異常統計條件」,則,直接熔斷服務,在「調用方」直接返回,不再 rpc 調用遠端服務;
- 降級:降級是配合「熔斷」的,熔斷後,不再調用遠端服務器的 rpc 接口,而採用本地的 fallback 機制,返回一個「備用方案」/「默認取值」;
- 限流:限制「速率」,或從業務層限制「總數」,被限流的請求,直接進入「降級」fallback 流程;
- 異步 RPC:通過異步訪問,提升系統訪問性能;
2.1.熔斷
爲了防止大量請求都被 block 到某個服務上,導致大量線程、端口被佔用,無法處理新請求,則,引入「服務熔斷」概念。
- 一旦觸發「異常統計條件」,則,不再 block 等待服務的響應
- 異常統計條件,一般有下述幾種:
- 指定的
時間窗口
內,rpc 調用失敗次數的佔比
,超過設定的閾值
,則,不再 rpc 調用,直接返回「降級邏輯」
- 指定的
HyStrix 框架下,實現「服務熔斷」的底層原理:
- 封裝:「客戶端」不直接調用「服務器」的rpc接口,而是在「客戶端」包裝一層
- 屏蔽:在「客戶端」的「包裝層」裏面,實現熔斷邏輯
具體 HyStrix 框架的 hello world 邏輯:
// 客戶端:封裝原生的 rpc 調用邏輯
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
//關鍵點:把一個RPC調用,封裝在一個HystrixCommand裏面
return "Hello " + name + "!";
}
}
// 客戶端調用:以前是直接調用遠端RPC接口,現在是把RPC接口封裝到HystrixCommand裏面,它內部完成熔斷邏輯
String s = new CommandHelloWorld("World").execute();
隔離策略:線程池 vs. 信號量
上面的代碼實例中,默認情況下,HystrixCommand
是「線程隔離策略」,即,直接在線程池中,獲取新的線程,來執行 rpc 調用。
另外,還有一種策略是「信號量隔離策略」,直接在「調用線程」中執行,通過「信號量」進行隔離。
Think:
上述隔離策略「線程池」和「信號量」,有什麼優劣?適用場景?
上述「線程池」和「信號量」2 種隔離方式,其優缺點:
- 線程池:
- 缺點:增加計算開銷,每個 rpc 請求,被獨立的線程執行,其中,涉及線程調度、上下文切換、請求排隊等時間。
- Note:Netflix 公司內部實踐認爲,線程隔離的開銷足夠小,不會產生重大成本或者性能影響。
- 適用:依賴網絡訪問的請求,可以採用「線程池隔離」,只依賴內存緩存的情況下,建議採用「信號量隔離」。
- 信號量:
- 適用:只依賴內存緩存、不涉及網絡訪問,建議採用「信號量隔離」方式。
熔斷參數的設置:熔斷器的參數
- circuitBreaker.requestVolumeThreshold://滑動窗口的大小,默認爲
20
- circuitBreaker.errorThresholdPercentage: //錯誤率,默認
50%
- circuitBreaker.sleepWindowInMilliseconds: //過多長時間,熔斷器再次檢測是否開啓,默認爲
5000
,即5s
鍾
上述 3 個參數,放在一起的物理含義:
每當
20
個請求中,有50%
失敗時,熔斷器就會斷開,此時,再調用此服務,將不再調遠程服務,直接返回失敗。
5s
後,重新檢測
該觸發條件,判斷是否熔斷器連接,或者繼續保持斷開。
2.2.降級
降級,是配合「熔斷」存在的。
降級,就是服務熔斷之後,不再調用服務器的 rpc 接口,客戶端直接準備一個本地的 fallback 回調,返回一個缺省值。
影響:
- 上述「服務降級」,直接本地 fallback,給一個
缺省值
/備用方案
- 相對直接掛掉,要好一些,具體還要看業務場景,特別是採用「合適的 fallback 方案」
2.3.限流
限流,在現實生活中,也比較常見,比如節假日去旅遊景點,管理部門通常會在外面設置攔截,限制景點的進入人數(等有人出來之後,再放新的人進去)。
對應到分佈式系統中,比如活動、秒殺,也會限流。
關鍵點:限流的目標和依據,是什麼?
常見的,限流依據
下述參數進行:
- HyStrix 中:
- 線程隔離時,線程數 + 排隊隊列大小,來限流
- 信號量隔離時,設置「最大併發請求數」,來限流
- 併發數量:QPS、併發連接數等
- 總量:業務層中,限制「庫存」總量等
限流技術原理:
- 限制「速率」:令牌桶算法
- 限制「總數」:限制某個業務量的 count 值,則,具體業務場景具體分析。
TODO:
- Guava 的
RateLimiter
也已經有成熟做法。
2.4.異步 RPC
異步 RPC,主要目標:提升系統的接口性能
,從而提升併發處理能力。
啓用「異步 RPC」,有一個前提,異步 RPC 之間,不存在「相互依賴」。
實例:
- 比如你的接口,內部調用了3個服務,時間分別爲T1, T2, T3。
- 如果是順序調用,則總時間是T1 + T2 + T3;
- 如果併發調用,總時間是Max(T1,T2,T3)。
一般成熟的RPC框架,本身都提高了異步化接口,Future或者Callback形式。
同樣,Hystrix 也提高了同步調用、異步調用方式,此處不再詳述。
3.總結
熔斷、降級、限流、異步 RPC,都是分佈式服務框架中,常用的策略,能夠提升系統穩定性、容錯性。當前大部分服務框架,都對這些策略,有比較好的原生支持。