1. Sentinel 是什麼?java
隨着微服務的流行,服務和服務之間的穩定性變得愈來愈重要。Sentinel 以流量爲切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。node
Sentinel 具備如下特徵:git
Sentinel 的主要特性:github
Sentinel 的開源生態:web
Sentinel 分爲兩個部分:spring
2. Sentinel 快速開始數據庫
首先,引入 Sentinel 依賴apache
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
接着,定義資源網絡
資源 是 Sentinel 中的核心概念之一。最經常使用的資源是咱們代碼中的 Java 方法。 固然,您也能夠更靈活的定義你的資源,例如,把須要控制流量的代碼用 Sentinel API SphU.entry("HelloWorld") 和 entry.exit() 包圍起來便可。在下面的例子中,咱們將 System.out.println("hello world"); 做爲資源(被保護的邏輯),用 API 包裝起來。例如:多線程
try (Entry entry = SphU.entry("HelloWorld")) {
// Your business logic here.
System.out.println("hello world");
} catch (BlockException e) {
// Handle rejected request.
e.printStackTrace();
}
// try-with-resources auto exit
還可使用註解定義資源 https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
例如:
@SentinelResource("HelloWorld")
public void helloWorld() {
// 資源中的邏輯
System.out.println("hello world");
}
最後,定義規則
接下來,經過流控規則來指定容許該資源經過的請求次數,例以下面的代碼定義了資源 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);
}
完成!
完整的代碼以下:
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <parent>
6 <groupId>org.springframework.boot</groupId>
7 <artifactId>spring-boot-starter-parent</artifactId>
8 <version>2.2.2.RELEASE</version>
9 <relativePath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupId>com.cjs.example</groupId>
12 <artifactId>sentinel-example</artifactId>
13 <version>0.0.1-SNAPSHOT</version>
14 <name>sentinel-example</name>
15
16 <properties>
17 <java.version>1.8</java.version>
18 <spring-cloud.version>Greenwich.SR4</spring-cloud.version>
19 <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
20 </properties>
21
22 <dependencies>
23 <dependency>
24 <groupId>org.springframework.boot</groupId>
25 <artifactId>spring-boot-starter-actuator</artifactId>
26 </dependency>
27 <dependency>
28 <groupId>org.springframework.boot</groupId>
29 <artifactId>spring-boot-starter-web</artifactId>
30 </dependency>
31 <dependency>
32 <groupId>com.alibaba.cloud</groupId>
33 <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
34 </dependency>
35
36 </dependencies>
37
38 <dependencyManagement>
39 <dependencies>
40 <dependency>
41 <groupId>org.springframework.cloud</groupId>
42 <artifactId>spring-cloud-dependencies</artifactId>
43 <version>${spring-cloud.version}</version>
44 <type>pom</type>
45 <scope>import</scope>
46 </dependency>
47
48 <dependency>
49 <groupId>com.alibaba.cloud</groupId>
50 <artifactId>spring-cloud-alibaba-dependencies</artifactId>
51 <version>${spring-cloud-alibaba.version}</version>
52 <type>pom</type>
53 <scope>import</scope>
54 </dependency>
55 </dependencies>
56 </dependencyManagement>
57
58 <build>
59 <plugins>
60 <plugin>
61 <groupId>org.springframework.boot</groupId>
62 <artifactId>spring-boot-maven-plugin</artifactId>
63 </plugin>
64 </plugins>
65 </build>
66
67 </project>
application.properties
server.port=8084
spring.application.name=sentinel-example
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8080
SentinelExampleApplication.java
1 package com.cjs.example.sentinel;
2
3 import com.alibaba.csp.sentinel.Entry;
4 import com.alibaba.csp.sentinel.SphU;
5 import com.alibaba.csp.sentinel.slots.block.BlockException;
6 import com.alibaba.csp.sentinel.slots.block.RuleConstant;
7 import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
8 import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
9 import org.springframework.boot.SpringApplication;
10 import org.springframework.boot.autoconfigure.SpringBootApplication;
11
12 import java.util.ArrayList;
13 import java.util.List;
14
15 @SpringBootApplication
16 public class SentinelExampleApplication {
17
18 public static void main(String[] args) {
19 SpringApplication.run(SentinelExampleApplication.class, args);
20
21
22 // 配置規則.
23 initFlowRules();
24
25 while (true) {
26 // 1.5.0 版本開始能夠直接利用 try-with-resources 特性,自動 exit entry
27 try (Entry entry = SphU.entry("HelloWorld")) {
28 // 被保護的邏輯
29 System.out.println("hello world");
30 } catch (BlockException ex) {
31 // 處理被流控的邏輯
32 System.out.println("blocked!");
33 }
34 }
35 }
36
37
38 private static void initFlowRules() {
39 List<FlowRule> rules = new ArrayList<>();
40
41 FlowRule rule = new FlowRule();
42 rule.setResource("HelloWorld");
43 rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
44 // Set limit QPS to 20.
45 rule.setCount(20);
46 rules.add(rule);
47
48 FlowRuleManager.loadRules(rules);
49
50 }
51 }
TestController.java
1 package com.cjs.example.sentinel;
2
3 import com.alibaba.csp.sentinel.annotation.SentinelResource;
4 import org.springframework.web.bind.annotation.GetMapping;
5 import org.springframework.web.bind.annotation.RestController;
6
7 @RestController
8 public class TestController {
9
10 @GetMapping("/hello")
11 @SentinelResource("hello")
12 public String hello() {
13 return "hello";
14 }
15
16 }
3. Sentinel 控制檯
Sentinel 控制檯最少應該包含以下功能:
https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
獲取控制檯:
方式一:下載已經打好的包
https://github.com/alibaba/Sentinel/releases
wget https://github.com/alibaba/Sentinel/releases/download/1.7.1/sentinel-dashboard-1.7.1.jar
方式二:經過源碼構件
mvn clean package
啓動控制檯
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar
默認用戶名密碼都是sentinel
4. Sentinel 註解支持
@SentinelResource 用於定義資源,並提供可選的異常處理和 fallback 配置項
@SentinelResource 註解的屬性:
exceptionsToIgnore
裏面排除掉的異常類型)進行處理。fallback 函數簽名和位置要求:defaultFallback :默認的 fallback 函數名稱,可選項,一般用於通用的 fallback 邏輯
Throwable
類型的參數用於接收對應的異常;fallbackClass
爲對應的類的 Class
對象,注意對應的函數必需爲 static 函數,不然沒法解析; 須要注意的是,若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException 時只會進入 blockHandler 處理邏輯。若未配置 blockHandler、fallback 和 defaultFallback,則被限流降級時會將 BlockException 直接拋出(若方法自己未定義 throws BlockException 則會被 JVM 包裝一層 UndeclaredThrowableException)。
示例:
1 public class TestService {
2
3 // 對應的 `handleException` 函數須要位於 `ExceptionUtil` 類中,而且必須爲 static 函數.
4 @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
5 public void test() {
6 System.out.println("Test");
7 }
8
9 // 原函數
10 @SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
11 public String hello(long s) {
12 return String.format("Hello at %d", s);
13 }
14
15 // Fallback 函數,函數簽名與原函數一致或加一個 Throwable 類型的參數.
16 public String helloFallback(long s) {
17 return String.format("Halooooo %d", s);
18 }
19
20 // Block 異常處理函數,參數最後多一個 BlockException,其他與原函數一致.
21 public String exceptionHandler(long s, BlockException ex) {
22 // Do some log here.
23 ex.printStackTrace();
24 return "Oops, error occurred at " + s;
25 }
26 }
能夠看到,blockHandler和fallback必須與原方法在同一個類中。若是不想寫在同一個類中,能夠利用blockHandlerClass來指定類,而後經過blockHandler指定方法名。
若是同時配置了blockHandler和fallback,則BlockException只會進到blockHandler處理邏輯中。
5. Sentinel 基本概念
資源
只要經過 Sentinel API 定義的代碼,就是資源,可以被 Sentinel 保護起來。大部分狀況下,可使用方法簽名,URL,甚至服務名稱做爲資源名來標示資源。
規則
圍繞資源的實時狀態設定的規則,能夠包括流量控制規則、熔斷降級規則以及系統保護規則。全部規則能夠動態實時調整。
流量控制
流量控制在網絡傳輸中是一個經常使用的概念,它用於調整網絡包的發送數據。然而,從系統穩定性角度考慮,在處理請求的速度上,也有很是多的講究。任意時間到來的請求每每是隨機不可控的,而系統的處理能力是有限的。咱們須要根據系統的處理能力對流量進行控制。Sentinel 做爲一個調配器,能夠根據須要把隨機的請求調整成合適的形狀,以下圖所示:
流量控制有如下幾個角度:
Sentinel 的設計理念是讓您自由選擇控制的角度,並進行靈活組合,從而達到想要的效果。
熔斷降級
Sentinel 和 Hystrix 的原則是一致的: 當檢測到調用鏈路中某個資源出現不穩定的表現,例如請求響應時間長或異常比例升高的時候,則對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而致使級聯故障。
在限制的手段上,Sentinel 和 Hystrix 採起了徹底不同的方法。
Hystrix 經過 線程池隔離 的方式,來對依賴(在 Sentinel 的概念中對應 資源)進行了隔離。這樣作的好處是資源和資源之間作到了最完全的隔離。缺點是除了增長了線程切換的成本(過多的線程池致使線程數目過多),還須要預先給各個資源作線程池大小的分配。 以下圖:
Sentinel 對這個問題採起了兩種手段:
和資源池隔離的方法不一樣,Sentinel 經過限制資源併發線程的數量,來減小不穩定資源對其它資源的影響。這樣不但沒有線程切換的損耗,也不須要您預先分配線程池的大小。當某個資源出現不穩定的狀況下,例如響應時間變長,對資源的直接影響就是會形成線程數的逐步堆積。當線程數在特定資源上堆積到必定的數量以後,對該資源的新請求就會被拒絕。堆積的線程完成任務後纔開始繼續接收請求。
除了對併發線程數進行控制之外,Sentinel 還能夠經過響應時間來快速降級不穩定的資源。當依賴的資源出現響應時間過長後,全部對該資源的訪問都會被直接拒絕,直到過了指定的時間窗口以後才從新恢復。
系統負載保護
Sentinel 同時提供系統維度的自適應保護能力。防止雪崩,是系統防禦中重要的一環。當系統負載較高的時候,若是還持續讓請求進入,可能會致使系統崩潰,沒法響應。在集羣環境下,網絡負載均衡會把本應這臺機器承載的流量轉發到其它的機器上去。若是這個時候其它的機器也處在一個邊緣狀態的時候,這個增長的流量就會致使這臺機器也崩潰,最後致使整個集羣不可用。
針對這個狀況,Sentinel 提供了對應的保護機制,讓系統的入口流量和系統的負載達到一個平衡,保證系統在能力範圍以內處理最多的請求。
6. 如何使用Sentinel
Sentinel 能夠簡單的分爲 Sentinel 核心庫和 Dashboard。核心庫不依賴 Dashboard,可是結合 Dashboard 能夠取得最好的效果。
資源,能夠是任何東西,服務,服務裏的方法,甚至是一段代碼。使用 Sentinel 來進行資源保護,主要分爲幾個步驟:
在編碼的時候,只須要考慮這個代碼是否須要保護,若是須要保護,就將之定義爲一個資源。
定義資源的經常使用方式
方式一: 拋出異常的方式定義資源
SphU
包含了 try-catch 風格的 API。用這種方式,當資源發生了限流以後會拋出 BlockException
。這個時候能夠捕捉異常,進行限流以後的邏輯處理。示例代碼以下:
1 // 1.5.0 版本開始能夠利用 try-with-resources 特性
2 // 資源名可以使用任意有業務語義的字符串,好比方法名、接口名或其它可惟一標識的字符串。
3 try (Entry entry = SphU.entry("resourceName")) {
4 // 被保護的業務邏輯
5 // do something here...
6 } catch (BlockException ex) {
7 // 資源訪問阻止,被限流或被降級
8 // 在此處進行相應的處理操做
9 }
特別地,若 entry 的時候傳入了熱點參數,那麼 exit 的時候也必定要帶上對應的參數(exit(count, args)),不然可能會有統計錯誤。這個時候不能使用 try-with-resources 的方式。另外經過 Tracer.trace(ex) 來統計異常信息時,因爲 try-with-resources 語法中 catch 調用順序的問題,會致使沒法正確統計異常數,所以統計異常信息時也不能在 try-with-resources 的 catch 塊中調用 Tracer.trace(ex)。
手動 exit 示例:
1 Entry entry = null;
2 // 務必保證 finally 會被執行
3 try {
4 // 資源名可以使用任意有業務語義的字符串,注意數目不能太多(超過 1K),超出幾千請做爲參數傳入而不要直接做爲資源名
5 // EntryType 表明流量類型(inbound/outbound),其中系統規則只對 IN 類型的埋點生效
6 entry = SphU.entry("自定義資源名");
7 // 被保護的業務邏輯
8 // do something...
9 } catch (BlockException ex) {
10 // 資源訪問阻止,被限流或被降級
11 // 進行相應的處理操做
12 } catch (Exception ex) {
13 // 若須要配置降級規則,須要經過這種方式記錄業務異常
14 Tracer.traceEntry(ex, entry);
15 } finally {
16 // 務必保證 exit,務必保證每一個 entry 與 exit 配對
17 if (entry != null) {
18 entry.exit();
19 }
20 }
熱點參數埋點示例:
1 Entry entry = null;
2 try {
3 // 若須要配置例外項,則傳入的參數只支持基本類型。
4 // EntryType 表明流量類型,其中系統規則只對 IN 類型的埋點生效
5 // count 大多數狀況都填 1,表明統計爲一次調用。
6 entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);
7 // Your logic here.
8 } catch (BlockException ex) {
9 // Handle request rejection.
10 } finally {
11 // 注意:exit 的時候也必定要帶上對應的參數,不然可能會有統計錯誤。
12 if (entry != null) {
13 entry.exit(1, paramA, paramB);
14 }
15 }
SphU.entry()
的參數描述:
方式2、註解方式定義資源
Sentinel 支持經過 @SentinelResource
註解定義資源並配置 blockHandler
和 fallback
函數來進行限流以後的處理。示例:
1 // 本來的業務方法.
2 @SentinelResource(blockHandler = "blockHandlerForGetUser")
3 public User getUserById(String id) {
4 throw new RuntimeException("getUserById command failed");
5 }
6
7 // blockHandler 函數,原方法調用被限流/降級/系統保護的時候調用
8 public User blockHandlerForGetUser(String id, BlockException ex) {
9 return new User("admin");
10 }
注意 blockHandler
函數會在原方法被限流/降級/系統保護的時候調用,而 fallback
函數會針對全部類型的異常。另外請注意 blockHandler
和 fallback
函數的形式要求。
方式3、支持異步調用
Sentinel 支持異步調用鏈路的統計。在異步調用中,須要經過 SphU.asyncEntry(xxx)
方法定義資源,並一般須要在異步的回調函數中調用 exit
方法。示例:
1 try {
2 AsyncEntry entry = SphU.asyncEntry(resourceName);
3
4 // 異步調用.
5 doAsync(userId, result -> {
6 try {
7 // 在此到處理異步調用的結果.
8 } finally {
9 // 在回調結束後 exit.
10 entry.exit();
11 }
12 });
13 } catch (BlockException ex) {
14 // Request blocked.
15 // Handle the exception (e.g. retry or fallback).
16 }
7. Sentinel 工做主流程
在 Sentinel 裏面,全部的資源都對應一個資源名稱(resourceName
),每次資源調用都會建立一個 Entry
對象。Entry 能夠經過對主流框架的適配自動建立,也能夠經過註解的方式或調用 SphU
API 顯式建立。Entry 建立的時候,同時也會建立一系列功能插槽(slot chain),這些插槽有不一樣的職責,例如:
整體架構圖以下:
這個彩色的圖貌似更好看一點兒
Sentinel 將 SlotChainBuilder
做爲 SPI 接口進行擴展,使得 Slot Chain 具有了擴展的能力。您能夠自行加入自定義的 slot 並編排 slot 間的順序,從而能夠給 Sentinel 添加自定義的功能。
8. Sentinel 流量控制
流量控制(flow control),其原理是監控應用流量的 QPS 或併發線程數等指標,當達到指定的閾值時對流量進行控制,以免被瞬時的流量高峯沖垮,從而保障應用的高可用性。
FlowSlot
會根據預設的規則,結合前面 NodeSelectorSlot
、ClusterNodeBuilderSlot
、StatisticSlot
統計出來的實時信息進行流量控制。
限流的直接表現是在執行 Entry nodeA = SphU.entry(resourceName)
的時候拋出 FlowException
異常。FlowException
是 BlockException
的子類,您能夠捕捉 BlockException
來自定義被限流以後的處理邏輯。
同一個資源能夠建立多條限流規則。FlowSlot
會對該資源的全部限流規則依次遍歷,直到有規則觸發限流或者全部規則遍歷完畢。
一條限流規則主要由下面幾個因素組成,咱們能夠組合這些元素來實現不一樣的限流效果:
8.1. 基於QPS/併發數的流量控制
流量控制主要有兩種統計類型,一種是統計併發線程數,另一種則是統計 QPS。類型由 FlowRule
的 grade
字段來定義。其中,0 表明根據併發數量來限流,1 表明根據 QPS 來進行流量控制。其中線程數、QPS 值,都是由 StatisticSlot
實時統計獲取的。
能夠經過下面的命令查看實時統計信息:
curl http://localhost:8719/cnode?id=resourceName
併發線程數流量控制
併發線程數限流用於保護業務線程數不被耗盡。例如,當應用所依賴的下游應用因爲某種緣由致使服務不穩定、響應延遲增長,對於調用者來講,意味着吞吐量降低和更多的線程數佔用,極端狀況下甚至致使線程池耗盡。爲應對太多線程佔用的狀況,業內有使用隔離的方案,好比經過不一樣業務邏輯使用不一樣線程池來隔離業務自身之間的資源爭搶(線程池隔離)。這種隔離方案雖然隔離性比較好,可是代價就是線程數目太多,線程上下文切換的 overhead 比較大,特別是對低延時的調用有比較大的影響。Sentinel 併發線程數限流不負責建立和管理線程池,而是簡單統計當前請求上下文的線程數目,若是超出閾值,新的請求會被當即拒絕,效果相似於信號量隔離。
QPS流量控制
當 QPS 超過某個閾值的時候,則採起措施進行流量控制。流量控制的效果包括如下幾種:直接拒絕、Warm Up、勻速排隊。對應 FlowRule
中的 controlBehavior
字段。
8.2. 基於調用關係的流量控制
調用關係包括調用方、被調用方;一個方法又可能會調用其它方法,造成一個調用鏈路的層次關係。Sentinel 經過 NodeSelectorSlot
創建不一樣資源間的調用的關係,而且經過 ClusterNodeBuilderSlot
記錄每一個資源的實時統計信息。
有了調用鏈路的統計信息,咱們能夠衍生出多種流量控制手段。
根據調用方限流
ContextUtil.enter(resourceName, origin)
方法中的 origin
參數標明瞭調用方身份。這些信息會在 ClusterBuilderSlot
中被統計。可經過如下命令來展現不一樣的調用方對同一個資源的調用數據:
根據調用鏈路入口限流:鏈路限流
NodeSelectorSlot
中記錄了資源之間的調用鏈路,這些資源經過調用關係,相互之間構成一棵調用樹。這棵樹的根節點是一個名字爲 machine-root
的虛擬節點,調用鏈的入口都是這個虛節點的子節點。
一棵典型的調用樹以下圖所示:
具備關係的資源流量控制:關聯流量控制
當兩個資源之間具備資源爭搶或者依賴關係的時候,這兩個資源便具備了關聯。好比對數據庫同一個字段的讀操做和寫操做存在爭搶,讀的速度太高會影響寫得速度,寫的速度太高會影響讀的速度。若是聽任讀寫操做爭搶資源,則爭搶自己帶來的開銷會下降總體的吞吐量。可以使用關聯限流來避免具備關聯關係的資源之間過分的爭搶,舉例來講,read_db
和 write_db
這兩個資源分別表明數據庫讀寫,咱們能夠給 read_db
設置限流規則來達到寫優先的目的:設置 FlowRule.strategy
爲 RuleConstant.RELATE
同時設置 FlowRule.ref_identity
爲 write_db
。這樣當寫庫操做過於頻繁時,讀數據的請求會被限流。
9. Sentinel 文檔
https://github.com/alibaba/Sentinel
https://github.com/alibaba/Sentinel/wiki/%E6%96%B0%E6%89%8B%E6%8C%87%E5%8D%97
https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
https://github.com/alibaba/Sentinel/wiki/Sentinel%E5%B7%A5%E4%BD%9C%E4%B8%BB%E6%B5%81%E7%A8%8B
https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
https://github.com/alibaba/Sentinel/tree/master/sentinel-demo
https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel