Spring Cloud Alibaba(七)——Sentinel流量控制框架
1. Sentinel簡介
Sentinel被稱為分布式系統的流量防衛(wèi)兵,是阿里開源流控框架,從服務限流、降級、熔斷等多個維度保護服務,Sentinel提供了簡潔易用的控制臺,可以看到接入應用的秒級數據,并且可以在控制臺設置一些規(guī)則保護應用,它比Hystrix支持的范圍廣,如Spring Cloud、Dubbo、gRPC都可以整合。
資源是Sentinel最關鍵的概念,遵循Sentinel API的開發(fā)規(guī)范定義資源,就能將應用保護起來。
而規(guī)則可以通過控制面板配置,也可以和資源聯合起來,規(guī)則可以在控制臺修改并且即時生效。
名詞解釋:
限流:不能讓流量一窩蜂的進來,否則可能會沖垮系統,需要限載流量,一般采用排隊的方式有序進行
對應生活中的小例子:比如在一個景區(qū),一般是不會讓所有人在同一時間進去的,會限制人數,排隊進入,讓景區(qū)內人數在一個可控范圍,因為景區(qū)的基礎設施服務不了那么多人。
降級:即使在系統出故障的情況下,也要盡可能的提供服務,在可用和不可用之間找一個平衡點,比如返回友好提示等。
例如現在稍有規(guī)模的電商系統,為了給用戶提供個性化服務,一般都有推薦系統,假設現在推薦系統宕機了,不應該在推薦商品一欄不給用戶展示商品,反而可以降低一點要求,保證給用戶看到的是友好界面,給用戶返回一些準備好的靜態(tài)數據。
熔斷:直接拒絕訪問,然后返回友好提示,一般是根據請求失敗率或請求響應時間做熔斷。
熔斷好比家里的保險盒,當線路過熱時,就會跳閘,以免燒壞電路。
2. Sentinel和同類產品對比
Sentinel、Hystrix、Resilience4j的異同
基礎特性 Sentinel Hystrix Resilience4j
限流 QPS、線程數、調用關系 有限的支持 Rate LImiter
注解的支持 支持 支持 支持
動態(tài)規(guī)則配置 支持多種數據源 支持多種數據源 有限支持
實時統計信息 滑動窗口 滑動窗口 Ring Bit Buffer
熔斷降級策略 平均響應時間、異常比例、異常數 異常比例 平均響應時間、異常比例
控制臺 可配置各種規(guī)則,接口調用的秒級信息,機器發(fā)現等 簡單監(jiān)控 不提供控制臺,可對接其它監(jiān)控平臺
流量整形 支持預熱、排隊模式 不支持 簡單的Rate Limiter模式
系統自適應限流 支持 不支持 不支持
擴展性 多個擴展點 插件的形式 接口的形式
常用適配框架 Servlet、Spring Cloud、Dubbo、gRPC等 Servlet、Spring Cloud、Netflix Spring Boot、Spring Cloud
3. 下載和運行
github地址
github.com/alibaba/Sen…
可以直接下載jar包運行jar包,也可以下載源碼編譯運行
因為它是springboot項目,下載jar包后直接運行jar包即可
java -jar sentinel-dashboard-1.8.3.jar
復制代碼
默認端口8080,如果需要修改,可以增加-Dserver.port參數,啟動命令修改為java -jar -Dserver.port=9000 sentinel-dashboard-1.8.3.jar ,即可將程序端口改為9000
默認賬號sentinel,默認密碼sentinel,登錄后頁面是空白的,是因為sentinel采用懶加載的方式,只有證正使用它,功能才會展示出來
4. 項目集成Sentinel
4.1 創(chuàng)建提供者服務
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 健康監(jiān)控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 服務注冊/服務發(fā)現需要引入的 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心依賴 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- sentinel組件依賴 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
bootstrap.yml
server:
port: 8082 #程序端口號
spring:
application:
name: provider # 應用名稱
cloud:
sentinel:
transport:
port: 8719 # 啟動HTTP Server,并且該服務與Sentinel儀表進行交互,是Sentinel儀表盤可以控制應用,如被占用,則從8719依次+1掃描
dashboard: 127.0.0.1:8080 # 指定儀表盤地址
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服務注冊、發(fā)現地址
config:
server-addr: 127.0.0.1:8848 # nacos配置中心地址
file-extension: yml # 指定配置內容的數據格式
management:
endpoints:
web:
exposure:
include: '*' # 公開所有端點
寫一個簡單的Controller給消費者調用
@RestController
public class TestController {
@GetMapping("/test")
public String test(){
return "provider test方法" + RandomUtils.nextInt(0,100);
}
}
4.2 創(chuàng)建消費端服務
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 健康監(jiān)控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 服務注冊/服務發(fā)現需要引入的 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos配置中心依賴 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- sentinel組件依賴 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
bootstrap.yml
server:
port: 8081 #程序端口號
spring:
application:
name: consumer # 應用名稱
cloud:
sentinel:
transport:
port: 8719 # 啟動HTTP Server,并且該服務與Sentinel儀表進行交互,是Sentinel儀表盤可以控制應用,如被占用,則從8719依次+1掃描
dashboard: 127.0.0.1:8080 # 指定儀表盤地址
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos服務注冊、發(fā)現地址
config:
server-addr: 127.0.0.1:8848 # nacos配置中心地址
file-extension: yml # 指定配置內容的數據格式
management:
endpoints:
web:
exposure:
include: '*' # 公開所有端點
新建一個controller去調用服務提供者
@RestController
public class TestController {
// 這里的服務地址填寫注冊到Nacos的應用名稱
private final String SERVER_URL = "http://provider";
@Resource
private RestTemplate restTemplate;
/**
* 調用提供者test接口
* @return
*/
@GetMapping("/test")
public String test(){
// 調用提供者的 /test 接口
return restTemplate.getForObject(SERVER_URL+"/test",String.class);
}
/**
* sentinel測試組件
* @return
*/
@GetMapping("/sentinelTest")
public String sentinelTest(){
return "TestController#sentinelTest " + RandomUtils.nextInt(0,10000);
}
}
4.3 使用RestTemplate+Ribbon遠程調用
增加config配置類,實例化RestTemplate對象
@Configuration
public class GenericConfiguration {
@LoadBalanced//標記此注解后,RestTemplate就具有了客戶端負載均衡的能力
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
在沒有加任何Sentinel規(guī)則的情況下接口正常調用
C:\Users\81950>curl http://localhost:8081/test
provider test方法13
C:\Users\81950>curl http://localhost:8081/test
provider test方法47
C:\Users\81950>curl http://localhost:8081/sentinelTest
TestController#sentinelTest 8873
C:\Users\81950>curl http://localhost:8081/sentinelTest
TestController#sentinelTest 3357
4.4 使用Sentinel常用規(guī)則
調用過接口之后,Sentinel控制臺出現了很多共功能
在使用Sentinel功能前,需要準備一個模擬發(fā)起請求的工具,測試限流這些規(guī)則效果用curl手動發(fā)起請求不太現實,這里將用到JMeter發(fā)起大規(guī)模請求。
下載地址
jmeter.apache.org/download_jm…
4.4.1 流控規(guī)則
1、流控規(guī)則主要是設置QPS或線程數等參數保護應用,針對某個資源的設置。添加規(guī)則前需要先調用接口。
一些流控關鍵詞的含義:
資源名:資源名稱,唯一即可
針對來源:對調用者進行限流,填寫應用名稱(一般是spring.application.name的值),指定對哪個服務進行限流(默認default是全部限制)
閾值類型
QPS:每秒能接受的請求數
線程數:能使用的業(yè)務線程數
流控模式
直接:達到條件后,直接執(zhí)行某個流控效果
關聯:如果關聯資源達到條件,就限流自身
鏈路:記錄從入口資源的流量,達到條件也只限流入口資源
流控效果
快速失?。哼_到條件后,直接返回失敗的結果
Warm Up:預熱,給一個緩沖時間,初始值是閾值/codeFactor(默認為3),慢慢達到設置的閾值
排隊等待:讓系統勻速處理請求,而不是一次處理很多,過一會則處于空閑狀態(tài)。
(1)QPS——直接——快速失敗
QPS(Query Per Second)是指每秒可處理的請求數。
流控規(guī)則設置
在Sentinel控制臺選擇“簇點鏈路”,選擇“列表視圖”,資源名為/sentinelTest進行流控
閥值類型為QPS,單機閾值為1
即增加了一條直接-快速失敗的流控規(guī)則
測試
使用JMeter設置線程數為10,發(fā)起請求
從結果可以看出,超過限制QPS超過閾值1就被接管了,直接返回失敗結果Blocked by Sentinel (flow limiting)
(2)QPS——直接——Warm Up
Warm Up是預熱,即初始i請求QPS等于閾值/coldFactor,cold-Factor的默認值為3,經過預熱時長1秒后單機閾值為100
流控規(guī)則設置
編輯流控規(guī)則,流控效果選擇Warm Up,預熱時長1秒,單機閾值100
測試
因為Jmeter線程數10,只循環(huán)依次,可能執(zhí)行完還不到1秒,所以把循環(huán)次數改為10,對比1秒前和1秒后的效果。
雖然預熱前1秒幾乎請求都是失敗的,但過了1秒后大部分都是請求成功的,流量緩慢增加,給冷系統一個緩沖時間,避免一下子把系統給壓垮。
(3)QPS——直接——排隊等待
讓請求以均勻的速度通過,如果請求超過了閾值就等待,如果等待超時就返回失敗
編輯流控規(guī)則,單機閾值依舊為1,超時時間15000毫秒,JMeter循環(huán)執(zhí)行次數1
QPS設置為1,在調用過程中幾乎是1秒發(fā)一個請求,超時時間如果短一些,一定會有很多失敗。
(4)QPS——關聯——快速失敗
如果訪問關聯接口B到達了閾值,就讓接口A返回失敗,這種規(guī)則適用于資源之間,具有資源爭搶或者依賴關系。
增加一個接口sentinelTestB
/**
* sentinel測試組件B
* @return
*/
@GetMapping("/sentinelTestB")
public String sentinelTestB(){
// 調用提供者的 /test 接口
return "TestController#sentinelTestB " + RandomUtils.nextInt(0,10000);
}
流控規(guī)則設置
修改流控規(guī)則,主要改的就是流控效果:關聯
測試
把JMeter循環(huán)次數設置為永遠,JMeter請求sentinelTestB接口,模擬一直超過閾值,然后使用curl命令請求sentinelTest接口,結果如下
C:\Users\81950>curl http://localhost:8081/sentinelTest
Blocked by Sentinel (flow limiting)
關聯資源B請求達到閾值,而請求sentinelTest接口直接被限流
(5)線程數——直接
限制處理請求的業(yè)務線程數,達到閾值就會限流
流控規(guī)則設置
閾值類型選擇“并發(fā)線程數”,單機閾值1,流控模式“直接”。
測試
JMeter線程數改為10,Ramp-Up時間為0.5,循環(huán)次數為10
從結果中看出很多請求被限流
因為設置的閾值很小,所以明顯業(yè)務線程已經處理不過來了,業(yè)務線程正在處理任務的時候再來的請求就被限流了。
4.4.2 熔斷規(guī)則
Sentinel1.8.0 及以上版本主要有三個策略:慢調用比例,異常比例,異常數。
(1)慢調用比例
選擇以慢調用比例作為閾值,需要設置允許的慢調用 RT(即最大的響應時間),請求的響應時間大于該值則統計為慢調用。當單位統計時長(statIntervalMs)內請求數目大于設置的最小請求數目,并且慢調用的比例大于閾值,則接下來的熔斷時長內請求會自動被熔斷。經過熔斷時長后熔斷器會進入探測恢復狀態(tài)(HALF-OPEN 狀態(tài)),若接下來的一個請求響應時間小于設置的慢調用 RT 則結束熔斷,若大于設置的慢調用 RT 則會再次被熔斷。
熔斷規(guī)則設置
同樣在簇點鏈路選擇列表視圖,對資源/sentinelTest,選擇熔斷,熔斷策略為慢比例調用,最大RT為500,比例閾值1,熔斷時長10秒,最小請求數5,統計時長1000ms
熔斷條件
在1000毫秒,也就是1秒內,如果發(fā)送到/sentinelTest的請求數數量大于5,并且在這些請求中,所有請求的響應時長(因為比例與閾值為1,所以是所有的請求響應時長)都大于500毫秒,也就是都大于0.5秒的時候,進入熔斷狀態(tài)。
模擬耗時操作
修改代碼,讓線程睡眠1秒
/**
* sentinel測試組件
* @return
*/
@GetMapping("/sentinelTest")
public String sentinelTest() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return "TestController#sentinelTest " + RandomUtils.nextInt(0,10000);
}
測試
JMeter設置線程數10,循環(huán)次數永遠
10個線程,在一秒的時間內全部發(fā)送完, 又因為接口響應時長為暫停1秒,所以響應一個請求的時長都大于一秒,所以在統計時長1000毫秒也就是1秒內會進入熔斷狀態(tài)
使用curl測試接口,此時已經進入熔斷狀態(tài)
C:\Users\81950>curl http://localhost:8081/sentinelTest
Blocked by Sentinel (flow limiting)
停止JMeter測試,超過熔斷時長10m后再使用curl測試,接口正常訪問
C:\Users\81950>curl http://localhost:8081/sentinelTest
TestController#sentinelTest 5483
(2)異常比例
當單位統計時長(statIntervalMs)內請求數目大于設置的最小請求數目,并且異常的比例大于閾值,則接下來的熔斷時長內請求會自動被熔斷。經過熔斷時長后熔斷器會進入探測恢復狀態(tài)(HALF-OPEN 狀態(tài)),若接下來的一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。異常比率的閾值范圍是 [0.0, 1.0],代表 0% - 100%。
熔斷規(guī)則設置
在1000毫秒(1秒)中當最少接收5個請求得情況下,錯誤率達到20%,接下來10秒開啟熔斷。
為了測試出效果,將接口故意使程序報錯
/**
* sentinel測試組件
* @return
*/
@GetMapping("/sentinelTest")
public String sentinelTest() throws InterruptedException {
// TimeUnit.SECONDS.sleep(1);
int i = 1/0; // 除數不能為0,此處必然會報錯
return "TestController#sentinelTest " + RandomUtils.nextInt(0,10000);
}
測試
JMeter設置1個線程1秒內執(zhí)行10次,查看結果
當異??倲当壤^設定的比例時,進入熔斷狀態(tài),要等過了時間窗口期10秒才能恢復
(3)異常數
當單位統計時長內的異常數目超過閾值之后會自動進行熔斷。經過熔斷時長后熔斷器會進入探測恢復狀態(tài)(HALF-OPEN 狀態(tài)),若接下來的一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。
熔斷規(guī)則設置
熔斷策略為異常數,異常數2,熔斷時長5秒,最小請求數5,表示如果1秒內異常數達到2個,則該接口再接下來的5秒鐘進入熔斷狀態(tài)
測試
JMeter使用1個線程1秒內執(zhí)行10次,結果如下
異常數到達兩個之后進入熔斷狀態(tài),要過了熔斷時長5秒后才能恢復。
4.4.3 系統規(guī)則
4.4.1流控規(guī)則和4.4.2降級規(guī)則是針對某個資源而言的,而系統規(guī)則是針對整個應用的,當前服務都會應用這個系統規(guī)則,相對來說就更加的粗粒度了,屬于應用級別的入口流量控制。
(1)LOAD
負載,當系統負載超過設定值,且并發(fā)線程數超過預估系統容量就會觸發(fā)保護機制。
此規(guī)則僅對Linux機器生效,因此將項目修改項目配置文件后嗎,打包放到服務器上運行。
系統規(guī)則設置
在系統規(guī)則界面,新增系統保護規(guī)則,閾值類型為LOAD,閾值為1
測試
使用JMeter使用100個線程調用接口,循環(huán)次數設置為永遠
(2)RT
整個應用上所有資源平均的響應時間,而不是某個固定資源
(3)線程數
設定真?zhèn)€應用所能使用的業(yè)務線程數閾值,而不是固定某個資源
(4)入口QPS
整個應用所使用的是每秒處理的請求數,而不是固定某個資源
(5)CPU使用率
應用占用CPU的百分比,同樣僅對Linux機器生效
都是超過設定值則被阻斷,不再演示
4.4.4 授權規(guī)則
授權規(guī)則是根據調用方判斷資源的請求是否被允許,Sentinel控制臺提供了黑白名單的授權類型,如果配置了白名單,表示只允許白名單的應用調用該資源時通過,如果配置了黑名單,表示黑名單的應用調用該資源不通過,其余的均能夠通過。
授權規(guī)則的配置需要服務提供者配置授權規(guī)則
創(chuàng)建CustomRequestOriginParser類來實現RequestOriginParser接口,用于獲取參數,然后將返回結果值交給Sentinel流控匹配處理
@Component
public class CustomRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// 區(qū)分來源:本質通過request域獲取來源標識
String origin = httpServletRequest.getParameter("origin");
// 授權規(guī)則必須判斷
if(StringUtils.isEmpty(origin)){
throw new RuntimeException("origin不能為空");
}
// 最后返回origin交給sentinel流控匹配處理
return origin;
}
}
新增授權規(guī)則
對/sentinelTestC資源新增授權,流控應用填寫app,授權類型黑名單
分別調用接口
C:\Users\81950>curl http://127.0.0.1:8081/sentinelTestC?origin=app
Blocked by Sentinel (flow limiting)
C:\Users\81950>curl http://127.0.0.1:8081/sentinelTestC?origin=pc
TestController#sentinelTestC 9667
配置了黑名單,表示黑名單的應用調用該資源不通過,其余的均能夠通過
給提供者增加CustomRequestOriginParser類,并通過遠程調用訪問test接口,對provider的/test資源新增授權規(guī)則
修改consumer的/test接口
/**
* 調用提供者test接口
* @return
*/
@GetMapping("/test")
public String test(){
// 調用提供者的 /test 接口
return restTemplate.getForObject(SERVER_URL+"/test?origin=consumer",String.class);
}
測試消費方的test接口,即攜帶origin遠程調用提供方的test接口,成功
C:\Users\81950>curl http://127.0.0.1:8081/test
provider test方法11
4.5 使用@SentinelResource注解
@SentinelResource注解根據實際情況實現定制化功能,對應用的保護更加細粒度。
之前到達一定閾值時,sentinel給的提示時Blocked by Sentinel (flow limiting),這顯然是不友好的,所以需要自定義錯誤頁面,又或者只針對某個參數限流等,實現更精細化的控制。
4.5.1 blockHandler屬性——負責響應控制面板配置
blockHandler主要是針對達到控制面板的限制條件做一個自定義的“兜底”操作,而不是返回默認的Blocked by Sentinel (flow limiting)。
添加一個接口/blockHandlerTest,資源名稱為blockHandlerTest,如果違反Sentinel控制臺的規(guī)則,就會自動進入blockHandlerTestHandler
@RestController
public class HandlerTestController {
@GetMapping("/blockHandlerTest")
// 資源名稱為blockHandlerTest 違反規(guī)則后的兜底方法是blockHandlerTestHandler
@SentinelResource(value = "blockHandlerTest", blockHandler = "blockHandlerTestHandler")
public String blockHandlerTest(String params) {
return "HandlerTestController#blockHandlerTest " + RandomUtils.nextInt(0, 1000);
}
/**
* 接口blockHandlerTest的兜底方法
*
* @param params
* @param blockException
* @return
*/
public String blockHandlerTestHandler(String params, BlockException blockException) {
return "HandlerTestController#blockHandlerTestHandler "
+ RandomUtils.nextInt(1, 1000)
+ " "
+ blockException.getMessage();
}
}
blockHandler指定的兜底方法的返回值類型要和原方法一致,并且該方法除了原有的參數(方法簽名),還要新增BlockExceptionca參數
新增流控規(guī)則
測試
JMeter使用10個線程循環(huán)10次
從結果可與i看到,發(fā)起第二個請求時,QPS已經達到1個了,然后進入到自定義的blockHandler方法
4.5.2 熱點規(guī)則
熱點就是在一定時期內訪問特別頻繁,如果訪問某個資源很頻繁,有可能只是某些參數訪問量很大。
Sentinel不僅支持以資源為粒度的限制,還可以更細化,針對資源下的參數進行限制,其實就是對這個接口的請求參數設置限定。
@RestController
public class HotspotTestController {
/**
* 熱點參數測試接口
*
* @return
*/
@GetMapping("/testHotKeyA")
@SentinelResource(value = "testHotKeyA", blockHandler = "blockTestHotKeyA")
public String testHotKeyA(
@RequestParam(value = "orderId", required = false) String orderId,
@RequestParam(value = "userId", required = false) String userId) {
return "HotspotTestController#testHotKeyA " + RandomUtils.nextInt(0, 1000);
}
/**
* 熱點參數測試接口testHotKeyA的兜底方法
* @param orderId
* @param userId
* @param blockException
* @return
*/
public String blockTestHotKeyA(String orderId, String userId, BlockException blockException) {
return "HotspotTestController#blockTestHotKeyA "
+ RandomUtils.nextInt(0, 1000)
+ " "
+ blockException.getMessage();
}
}
新增熱點規(guī)則
限流模式只能是QPS,參數索引為0,代表是orderId參數,單機閾值為1,統計窗口時長為5秒,也就是在5秒內統計到QPS大于1,接口就會被阻斷,進入到自定義的blockTestHotKeyA方法,
測試
JMeter使用10個線程循環(huán)10次測試testHotKeyA接口,需要加上orderId參數
從結果可以看出,發(fā)起第2個請求時,5秒內統計到的QPS已經大于1了,所以進入自定義的blockTestHotKeyA方法
另外還可以對參數的值單獨設置閾值,也可以添加多個值
JMeter中orderId傳值111,統計時間5秒內并不會達到限流閥值500,也就不會進入blockHandler方法
4.5.3 fallback屬性——負責業(yè)務異常
fallback屬性的方法是對業(yè)務異常的“兜底”,如果業(yè)務代碼報了異常(除了exceptionToIgnore屬性排除掉的異常類型),就會進入fallback屬性配置的方法。
增加fallbackTest接口代碼,定義fallback的方法fallbackHandler
@RestController
public class FallbackTestController {
/**
* 測試fallback的方法
* @param params
* @return
*/
@GetMapping("/fallbackTest")
// 資源名稱為fallbackTest,異常后的兜底方法為fallbackHandler
@SentinelResource(value = "fallbackTest",fallback = "fallbackHandler")
public String fallbackTest(String params) {
int res = 1 / 0; // 此處模擬報錯
return "FallbackTestController#fallbackTest "
+ RandomUtils.nextInt(0, 1000);
}
/**
* 接口fallbackTest的兜底方法
* @param params
* @return
*/
public String fallbackHandler(String params){
return "FallbackTestController#fallbackHandler "
+ RandomUtils.nextInt(0, 1000);
}
}
接口測試必然時報錯的,因為使用了fallback屬性設置了兜底方法,所以一報錯就進入fallbackHandlerfa方法
C:\Users\81950>curl http://127.0.0.1:8081/fallbackTest
FallbackTestController#fallbackHandler 704
4.5.4 fallback+blockHandler
增加sentinelUnionTest接口代碼,資源名為sentinelUnionTest,并且調用該接口是必然報錯的
@RestController
public class UnionTestController {
/**
* sentinel組件測試方法fallback和blockHandler聯合
* @return
*/
@GetMapping("/sentinelUnionTest")
@SentinelResource(
value = "sentinelUnionTest",
blockHandler = "sentinelUnionTestBlockHandler",
fallback = "sentinelUnionTestFallback")
public String sentinelUnionTest() {
int res = 1 / 0; // 此處必然報錯
return "UnionTestController#sentinelUnionTest "
+ RandomUtils.nextInt(0, 1000);
}
/**
* sentinelUnionTest的兜底方法
* @return
*/
public String sentinelUnionTestFallback() {
return "UnionTestController#sentinelUnionTestFallback "
+ RandomUtils.nextInt(0, 1000);
}
/**
* sentinelUnionTest的兜底方法
* @param blockException
* @return
*/
public String sentinelUnionTestBlockHandler(BlockException blockException) {
return "UnionTestController#sentinelUnionTestBlockHandler "
+ RandomUtils.nextInt(0, 1000)
+ " "
+ blockException.getMessage();
}
}
控制臺新增sentinelUnionTest資源的流控規(guī)則
JMeter使用10個線程循環(huán)10次調用/sentinelUnionTest接口
從結果可以看出,發(fā)出第一個請求時,并沒有達到QPS閾值的條件,方法內部報錯后,進入了fallback屬性定義的兜底方法,發(fā)送第二個請求時已經達到了QPS閾值條件,進入了blockHandler屬性定義的方法。
4.5.5 exceptionsToIgnore忽略異常
fallback可以針對所有的類型的異常,@SentinelResource注解的exceptionsToIgnore屬性表示忽略異常,不會納入異常統計,也就會跳過fallback屬性。
@RestController
public class ExceptionsTestController {
/**
* 測試exceptionsToIgnore的方法
* @return
*/
@GetMapping("/exceptionsToIgnoreTest")
@SentinelResource(
value = "exceptionsToIgnoreTest", // 資源名稱為exceptionsToIgnoreTest
fallback = "exceptionsToIgnoreTestFallback", // 異常后的兜底方法為exceptionsToIgnoreTestFallback
exceptionsToIgnore = {ArithmeticException.class} // 忽略ArithmeticException異常
)
public String exceptionsToIgnoreTest() {
int res = 1 / 0; // 此處模擬報錯
return "ExceptionsTestController#exceptionsToIgnoreTest "
+ RandomUtils.nextInt(0, 1000);
}
/**
* 接口exceptionsToIgnoreTest的兜底方法
* @return
*/
public String exceptionsToIgnoreTestFallback() {
return "ExceptionsTestController#exceptionsToIgnoreTestFallback "
+ RandomUtils.nextInt(0, 1000);
}
}
接口測試
1/0必然會拋出ArithmeticException算數異常,方法內異常本應該進入fallback定義的方法,因為設置了exceptionsToIgnores屬性,忽略了ArithmeticException異常,所以異常照常返回。
4.5.6 代碼優(yōu)化
4.5.4中的代碼中,fallback和blockHandler的處理方法都寫在同一個類里,一來這樣不符合程序單一的原則,畢竟controller層有很多之外的邏輯,二來別的類也不好復用其方法。
新需求:把具體的處理函數單獨放在一個類
@SentinelResource注解還有兩個屬性,分別是blockHandlerClass和fallbackClass,通過源碼查看其注釋
優(yōu)化代碼
為fallback建立單獨的類ExceptionHandler
public class ExceptionHandler {
/**
* 接口sentinelUnionTest的兜底方法,放到單獨類后必須時static
* @return
*/
public static String sentinelUnionTestFallback(){
return "單獨類ExceptionHandler#sentinelUnionTestFallback "
+ RandomUtils.nextInt(0,1000);
}
}
為blockHandler建立單獨的類BlockHandler
public class BlockHandler {
/**
* sentinelUnionTest的兜底方法,放到單獨類后必須時static
* @param blockException
* @return
*/
public static String sentinelUnionTestBlockHandler(BlockException blockException) {
return "單獨類BlockHandler#sentinelUnionTestBlockHandler "
+ RandomUtils.nextInt(0, 1000)
+ " "
+ blockException.getMessage();
}
}
修改UnionTestController中的代碼,指定fallback和blockHandler的類
@RestController
public class UnionTestOptimizeController {
/**
* sentinel組件測試方法fallback和blockHandler聯合
* 指定fallback和blockHandler的類
* @return
*/
@GetMapping("/sentinelUnionTestOptimize")
@SentinelResource(
value = "sentinelUnionTestOptimize",
blockHandler = "sentinelUnionTestBlockHandler",
blockHandlerClass = BlockHandler.class,
fallback = "sentinelUnionTestFallback",
fallbackClass = ExceptionHandler.class
)
public String sentinelUnionTest() {
int res = 1 / 0; // 此處必然報錯
return "UnionTestController#sentinelUnionTest "
+ RandomUtils.nextInt(0, 1000);
}
}
設置sentinelUnionTestOptimize資源流控規(guī)則,QPS為1,使用JMeter使用10個線程測試接口
結果與4.5.4中是一致的,在發(fā)出第一個請求時,沒有達到QPS閾值的條件,方法內部報錯,進入了fallback定義的方法,發(fā)出第二個請求的時候達到了QPS的閾值條件,直接進入blockHandler定義的方法,證明把fallback和blockHandler的函數移到單獨類中是可行的。
作者:碼出宇宙
歡迎關注微信公眾號 :碼出宇宙
掃描添加好友邀你進技術交流群,加我時注明【姓名+公司(學校)+職位】