聊聊Sentinel 快速入門
本文轉載自微信公眾號「運維開發故事」,作者老鄭。轉載本文請聯系運維開發故事公眾號。
Sentinel 簡介
隨著微服務的流行,服務和服務之間的穩定性變得越來越重要。Sentinel 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
Sentinel 具有以下特征:
- 豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的范圍)、消息削峰填谷、集群流量控制、實時熔斷下游不可用應用等。
- 完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制臺中看到接入應用的單臺機器秒級數據,甚至 500 臺以下規模的集群的匯總運行情況。
- 廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴并進行簡單的配置即可快速地接入 Sentinel。
- 完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您可以通過實現擴展接口來快速地定制邏輯。例如定制規則管理、適配動態數據源等。
Sentinel 的主要特性:
Sentinel 的開源生態:
Sentinel 分為兩個部分:
核心庫(Java 客戶端)不依賴任何框架/庫,能夠運行于所有 Java 運行時環境,同時對 Dubbo / Spring Cloud 等框架也有較好的支持。
控制臺(Dashboard)基于 Spring Boot 開發,打包后可以直接運行,不需要額外的 Tomcat 等應用容器。
Sentinel、Hystrix、resilience4j 對比
Sentinel |
Hystrix |
resilience4j |
|
隔離策略 |
信號量隔離(并發控制) |
線程池隔離/信號量隔離 |
信號量隔離 |
熔斷降級策略 |
基于慢調用比例、異常比例、異常數 |
基于異常比例 |
基于異常比例、響應時間 |
實時統計實現 |
滑動窗口(LeapArray) |
滑動窗口(基于 RxJava) |
Ring Bit Buffer |
動態規則配置 |
支持多種數據源 |
支持多種數據源 |
有限支持 |
擴展性 |
多個擴展點 |
插件的形式 |
接口的形式 |
基于注解的支持 |
支持 |
支持 |
支持 |
限流 |
基于 QPS,支持基于調用關系的限流 |
有限的支持 |
Rate Limiter |
流量整形 |
支持預熱模式與勻速排隊控制效果 |
不支持 |
簡單的 Rate Limiter 模式 |
系統自適應保護 |
支持 |
不支持 |
不支持 |
多語言支持 |
Java/Go/C++ |
Java |
Java |
Service Mesh 支持 |
支持 Envoy/Istio |
不支持 |
不支持 |
控制臺 |
提供開箱即用的控制臺,可配置規則、實時監控、機器發現等 |
簡單的監控查看 |
不提供控制臺,可對接其它監控系統 |
Sentinel 名詞
資源
資源是 Sentinel 的關鍵概念。它可以是 Java 應用程序中的任何內容,例如,由應用程序提供的服務,或由應用程序調用的其它應用提供的服務,甚至可以是一段代碼。在接下來的文檔中,我們都會用資源來描述代碼塊。
只要通過 Sentinel API 定義的代碼,就是資源,能夠被 Sentinel 保護起來。大部分情況下,可以使用方法簽名,URL,甚至服務名稱作為資源名來標示資源。
規則
圍繞資源的實時狀態設定的規則,可以包括流量控制規則、熔斷降級規則以及系統保護規則。所有規則可以動態實時調整。
流量控制
什么是流量控制
流量控制在網絡傳輸中是一個常用的概念,它用于調整網絡包的發送數據。然而,從系統穩定性角度考慮,在處理請求的速度上,也有非常多的講究。任意時間到來的請求往往是隨機不可控的,而系統的處理能力是有限的。我們需要根據系統的處理能力對流量進行控制。Sentinel 作為一個調配器,可以根據需要把隨機的請求調整成合適的形狀,如下圖所示:
流量控制設計理念
流量控制有以下幾個角度:
- 資源的調用關系,例如資源的調用鏈路,資源和資源之間的關系;
- 運行指標,例如 QPS、線程池、系統負載等;
- 控制的效果,例如直接限流、冷啟動、排隊等。
Sentinel 的設計理念是讓您自由選擇控制的角度,并進行靈活組合,從而達到想要的效果。
熔斷降級
什么是熔斷降級
除了流量控制以外,及時對調用鏈路中的不穩定因素進行熔斷也是 Sentinel 的使命之一。由于調用關系的復雜性,如果調用鏈路中的某個資源出現了不穩定,可能會導致請求發生堆積,進而導致級聯錯誤。
Sentinel 和 Hystrix 的原則是一致的: 當檢測到調用鏈路中某個資源出現不穩定的表現,例如請求響應時間長或異常比例升高的時候,則對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯故障。
熔斷降級設計理念
在限制的手段上,Sentinel 和 Hystrix 采取了完全不一樣的方法。
Hystrix 通過 線程池隔離 的方式,來對依賴(在 Sentinel 的概念中對應 資源)進行了隔離。這樣做的好處是資源和資源之間做到了最徹底的隔離。缺點是除了增加了線程切換的成本(過多的線程池導致線程數目過多),還需要預先給各個資源做線程池大小的分配。
Sentinel 對這個問題采取了兩種手段:
- 通過并發線程數進行限制
和資源池隔離的方法不同,Sentinel 通過限制資源并發線程的數量,來減少不穩定資源對其它資源的影響。這樣不但沒有線程切換的損耗,也不需要您預先分配線程池的大小。當某個資源出現不穩定的情況下,例如響應時間變長,對資源的直接影響就是會造成線程數的逐步堆積。當線程數在特定資源上堆積到一定的數量之后,對該資源的新請求就會被拒絕。堆積的線程完成任務后才開始繼續接收請求。
- 通過響應時間對資源進行降級
除了對并發線程數進行控制以外,Sentinel 還可以通過響應時間來快速降級不穩定的資源。當依賴的資源出現響應時間過長后,所有對該資源的訪問都會被直接拒絕,直到過了指定的時間窗口之后才重新恢復。
系統自適應保護
Sentinel 同時提供系統維度的自適應保護能力。防止雪崩,是系統防護中重要的一環。當系統負載較高的時候,如果還持續讓請求進入,可能會導致系統崩潰,無法響應。在集群環境下,網絡負載均衡會把本應這臺機器承載的流量轉發到其它的機器上去。如果這個時候其它的機器也處在一個邊緣狀態的時候,這個增加的流量就會導致這臺機器也崩潰,最后導致整個集群不可用。
針對這個情況,Sentinel 提供了對應的保護機制,讓系統的入口流量和系統的負載達到一個平衡,保證系統在能力范圍之內處理最多的請求。
Sentinel 原理
Sentinel 的主要工作機制如下:
- 對主流框架提供適配或者顯示的 API,來定義需要保護的資源,并提供設施對資源進行實時統計和調用鏈路分析。
- 根據預設的規則,結合對資源的實時統計信息,對流量進行控制。同時,Sentinel 提供開放的接口,方便您定義及改變規則。
- Sentinel 提供實時的監控系統,方便您快速了解目前系統的狀態。
Sentinel 使用
普通使用
如果應用使用 pom 工程,則在 pom.xml 文件中加入以下代碼即可:
- <dependency>
- <groupId>com.alibaba.csp</groupId>
- <artifactId>sentinel-core</artifactId>
- <version>1.8.1</version>
- </dependency>
接下來,我們把需要控制流量的代碼用 Sentinel API SphU.entry("HelloWorld") 和 entry.exit() 包圍起來即可。在下面的例子中,我們將 System.out.println("hello world"); 這端代碼作為資源,用 API 包圍起來(埋點)。參考代碼如下:
- while (true) {
- Entry entry = null;
- try {
- entry = SphU.entry("HelloWorld");
- /*您的業務邏輯 - 開始*/
- System.out.println("hello world");
- TimeUnit.MILLISECONDS.sleep(10);
- /*您的業務邏輯 - 結束*/
- } catch (BlockException e1) {
- /*流控邏輯處理 - 開始*/
- System.out.println("block!");
- /*流控邏輯處理 - 結束*/
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- if (entry != null) {
- entry.exit();
- }
- }
- }
接下來,通過規則來指定允許該資源通過的請求次數,例如下面的代碼定義了資源 HelloWorld 每秒最多只能通過 20 個請求。
- // 規則配置
- private static void initFlowRules() {
- List<FlowRule> rules = new ArrayList<>();
- FlowRule rule = new FlowRule();
- rule.setResource("HelloWorld");
- rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
- // Set limit QPS to 20.
- rule.setCount(20);
- rules.add(rule);
- FlowRuleManager.loadRules(rules);
- }
Demo 運行之后,我們可以在日志 ~/logs/csp/${appName}-metrics.log.xxx 里看到下面的輸出:
- |--timestamp-|------date time----|-resource-|p |block|s |e|rt
- 1619954886000|2021-05-02 19:28:06|HelloWorld|20|1|20|0|12|0|0|0
- 1619954887000|2021-05-02 19:28:07|HelloWorld|20|3197|20|0|11|0|0|0
- 1619954888000|2021-05-02 19:28:08|HelloWorld|20|2857|20|0|11|0|0|0
其中 p 代表通過的請求, block 代表被阻止的請求, s 代表成功執行完成的請求個數, e 代表用戶自定義的異常, rt 代表平均響應時長。
可以看到,這個程序每秒穩定輸出 "hello world" 20 次,和規則中預先設定的閾值是一樣的。
注解方式
Sentinel 提供了 @SentinelResource 注解用于定義資源,并提供了 AspectJ 的擴展用于自動定義資源、處理 BlockException 等。使用 Sentinel Annotation AspectJ Extension 的時候需要引入以下依賴:
- <dependency>
- <groupId>com.alibaba.csp</groupId>
- <artifactId>sentinel-annotation-aspectj</artifactId>
- <version>x.y.z</version>
- </dependency>
示例
- // 對應的 `handleException` 函數需要位于 `ExceptionUtil` 類中,并且必須為 public static 函數.
- // 對應的返回值也需要和當前方法一樣
- @SentinelResource(value = "createOrder",
- blockHandler = "blockHandler",
- blockHandlerClass = {ExceptionUtils.class})
- @GetMapping("/createOrder")
- public OrderDto createOrder(OrderDto dto) {
- return new OrderDto();
- }
- // ExceptionUtils
- public class ExceptionUtils {
- public static OrderDto blockHandler(OrderDto dto, BlockException ex) {
- ex.printStackTrace();
- return null;
- }
- }
@SentinelResource 注解
注意:注解方式埋點不支持 private 方法。
@SentinelResource 用于定義資源,并提供可選的異常處理和 fallback 配置項。@SentinelResource 注解包含以下屬性:
- value:資源名稱,必需項(不能為空)
- entryType:entry 類型,可選項(默認為 EntryType.OUT)
- blockHandler / blockHandlerClass: blockHandler 對應處理 BlockException 的函數名稱,可選項。blockHandler 函數訪問范圍需要是 public,返回類型需要與原方法相匹配,參數類型需要和原方法相匹配并且最后加一個額外的參數,類型為 BlockException。blockHandler 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 blockHandlerClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
- fallback / fallbackClass:fallback 函數名稱,可選項,用于在拋出異常的時候提供 fallback 處理邏輯。fallback 函數可以針對所有類型的異常(除了 exceptionsToIgnore 里面排除掉的異常類型)進行處理。fallback 函數簽名和位置要求:
- 返回值類型必須與原函數返回值類型一致;
- 方法參數列表需要和原函數一致,或者可以額外多一個 Throwable 類型的參數用于接收對應的異常。
- fallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
- defaultFallback(since 1.6.0):默認的 fallback 函數名稱,可選項,通常用于通用的 fallback 邏輯(即可以用于很多服務或方法)。默認 fallback 函數可以針對所有類型的異常(除了 exceptionsToIgnore 里面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則只有 fallback 會生效。defaultFallback 函數簽名要求:
- 返回值類型必須與原函數返回值類型一致;
- 方法參數列表需要為空,或者可以額外多一個 Throwable 類型的參數用于接收對應的異常。
- defaultFallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
- exceptionsToIgnore(since 1.6.0):用于指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣拋出。
1.8.0 版本開始,defaultFallback 支持在類級別進行配置。
注:1.6.0 之前的版本 fallback 函數只針對降級異常(DegradeException)進行處理,不能針對業務異常進行處理。
特別地,若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException 時只會進入 blockHandler 處理邏輯。若未配置 blockHandler、fallback 和 defaultFallback,則被限流降級時會將 BlockException 直接拋出(若方法本身未定義 throws BlockException 則會被 JVM 包裝一層 UndeclaredThrowableException)。
Sentinel 控制臺
下載控制臺程序地址:
- https://github.com/alibaba/Sentinel/releases/tag/1.8.1
啟動命令
- java -Dserver.port=8089
- -Dcsp.sentinel.dashboard.server=127.0.0.1:8089
- -Dproject.name=sentinel-dashboard
- -jar sentinel-dashboard-1.8.1.jar
登錄賬號,默認的登錄帳號和密碼都是:sentinel
登錄控制臺后我們可以通過右側菜單對我們的服務進行配置
參考
https://github.com/alibaba/Sentinel/wiki/介紹
https://github.com/Netflix/Hystrix/wiki/How-it-Works#benefits-of-thread-pools