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