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é)校)+職位】