天天看點

SpringCloud Alibaba 之SentinelSpringCloud Alibaba 之SentinelSentinel是什麼安裝Sentinel控制台初始化示範工程流控規則流控效果降級規則熱點key限流系統規則@SentinelResource配置服務熔斷功能規則持久化

SpringCloud Alibaba 之Sentinel

實作熔斷和限流

Sentinel是什麼

官網 https://github.com/alibaba/Sentinel

中文 https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

Hystrix 缺點

  1. 需要我們程式員自己手工搭建監控平台
  2. 沒有一套web界面可以給我們進行更加細粒化的配置

    流控、速率控制、服務熔斷、服務降級…

    Sentinel 單獨一個元件,可以獨立出來。

    直接界面化的細粒度統一配置。

去哪兒下? https://github.com/alibaba/Sentinel/releases

此次下載下傳 1.7.0 版本

能幹嘛?

怎麼玩?

​ 檢視github上官方文檔可以看How to Use Sentinel

​ 服務使用中的各種問題

​ 服務雪崩

​ 服務降級

​ 服務熔斷

​ 服務限流

安裝Sentinel控制台

sentinel元件由2部分構成:

​ 背景

​ 前台8080

​ 核心庫:(java用戶端)不依賴任何架構/庫,能夠運作于所有Java運作時環境,同時對 Dubbo / Spring Cloud等架構也有較好的支援

​ 控制台 :(Dashboard)基于 Spring Boot開發,打包後可以直接運作,不需要額外的Tomcat等應用容器。

安裝步驟

​ 下載下傳 https://github.com/alibaba/sentinel/releases

​ 下載下傳到本地 sentinel-dashboard-1.7.0.jar

​ 運作指令

​ 前提: java8環境OK 8080端口不能被占用

​ 指令: java -jar sentinel-dashboard-1.7.0.jar 在jar封包件夾cmd,輸入

​ 通路sentinel管理界面

​ http://localhost:8080

​ 登入賬号密碼為sentinel

初始化示範工程

​ 1、啟動Nacos8848成功 : http://localhost:8848/nacos/#/login

​ 2、Module cloudalibaba-sentinel-service8401

​ POM

<dependencies>
    <!--SpringCloud alibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!--SpringCloud alibaba sentinel - datasource-nacos  後續做持久化用到-->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>

    <!--SpringCloud alibaba sentinel-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>

    <!--openfeign 後續要用-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <!--SpringBoot 整合Web元件+actuator-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>


</dependencies>
           

​ YML

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服務注冊中心位址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard位址
        dashboard: localhost:8080
        #預設8719端口,假如被占用會自動從8719開始依次+1掃描,直至找到未被占用的端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'
           

​ 主啟動類

@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {

    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class, args);
    }

}
           

業務類 FlowLimitController

@RestController
public class FlowLimitController {

    @GetMapping("/testA")
    public String testA() {
        return "------testA";
    }

    @GetMapping("/testB")
    public String testB() {
        return "-----testB";
    }
    
}
           

​ 3、啟動Sentinel8080

​ java -jar sentinel-dashboard-1.7.0.jar

​ 4、啟動微服務8401

​ 5、啟動8401微服務後檢視sentinel控制台

​ 會發現背景空空如也,啥都沒有

​ Sentinel采用懶加載說明

​ 執行一次通路即可 :http://localhost:8401/testA

​ http://localhost:8401/testB

​ 效果 :應用開始出現在背景

​ 結論:Sentinel8080正在監控

流控規則

流控規則簡介

資源名:唯一名稱,預設請求路徑

針對來源:Sentinel可以針對調用者進行限流,填寫微服務名,預設default(不區分來源)

門檻值類型/單機門檻值:

​ QPS(每秒鐘的請求熟路):當調用該api的QPS達到門檻值的時候,進行限流

​ 線程數:當調用該api的線程數達到門檻值時候,進行限流

是否叢集:不需要叢集

流控模式:

​ 直接:api達到限流條件時,直接限流

​ 關聯:當關聯的資源達到門檻值時,就限流自己

​ 鍊路:隻記錄指定鍊路上的流量(指定資源從入口資源進來的流量,如果達到門檻值,就進行限流)

流控效果:

​ 快速失敗: 直接失敗,抛異常

​ Warm Up: 根據codeFactor (冷加載因子,預設3)的值,從門檻值/codeFactor,經過預熱時長,才達到設定的QPS門檻值

​ 排隊等待:勻速排隊,讓請求以勻速通過,門檻值類型必須設定為QPS,否則無效

QPS直接失敗

直接->快速失敗 系統預設:表示1秒鐘查詢次數在單機門檻值以内就是OK,超過就直接失敗,報預設錯誤

​ 預設設定的門檻值類型是QPS

​ 報錯資訊:Blocked by Sentinel (flow limiting)

​ 通路頻率降低後可以自動恢複。

​ 思考:直接調用預設報錯資訊,技術方面OK,但是but,是否應該有我們自己的後續處理?

​ 應該有個fallback方法,自己配置

線程數直接失敗

改造controller

@RestController
public class FlowLimitController {

    @GetMapping("/testA")
    public String test001(){
        try {
            TimeUnit.MILLISECONDS.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "testA";
    }

    @GetMapping("/testB")
    public String test002() {

        return "testB";
    }

}
           

中間犯得一次錯誤:注意:MICROSECONDS 和MILLSECONDS的差別 微秒和毫秒

重新開機後通路一次testA 和testB ,鍊路出現後設定流控:

設定鍊路流控:

​ 設定testA 為 門檻值類型:線程數,單機門檻值 1

傳回通路 多次點選後出現blocked by sentinel

線程數和QPS的差別:

​ QPS是直接拒絕,關在大門外,而線程數是放進來,但是同時隻能執行門檻值數,多了就不行了

關聯

​ 是什麼:當關聯的資源達到門檻值時,就限流自己

​ 當與A關聯的資源B達到門檻值後,就限流A自己

​ B惹事,A挂了

​ 如何配置:在進階選項裡設定 /testA和/testB的關聯

​ 設定效果:當關聯資源/testB的qbs門檻值達到1時,就限流/testA的Rest通路位址,當關聯資源達到門檻值後閑置配置好的資源名

​ postman模拟并發秘籍通路testB

​ 先試着通路testB ,可以通路,點選框的最右側的save as ,儲存進folder,

​ 第二步:點選檔案夾右邊三個點上方的右向箭頭,出現collection資訊,點選相應的請求後,點選上方的Run按鈕

​ 第三步:上面步驟點選Run後出現Collection Runner界面,可以設定請求的資訊,可以設定多少次通路,間隔多長時間

​ run: 大批量線程高并發通路B,導緻A失效了

點選通路 testA 結果發現:blocked by sentinel (flow limiting)

注意(容易錯的地方):誰關聯誰?誰出錯?

​ 在監控中,設定資源名 /testA 關聯 /testB,也就是下方的那個

​ 在postman中 大批量密集通路 /testB,然後去通路/testA,發現/testA失效 ,常犯錯誤就是設定反了

流控效果

直接

​ 直接-> 快速失敗(預設的流控處理)

​ 直接失敗,抛出異常 blocked by Sentinel(flow limiting)

​ 源碼 com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

預熱

​ 說明:公式:門檻值除以coldFactor(預設是3),經過預熱時長後才會達到門檻值

​ Warm Up 方式,即預熱/冷啟動方式。當系統長期處于低水位情況下,當流量突然增加時,直接把系統拉升到高水位可能瞬間把系統壓垮。通過“冷啟動”,讓通過的流量緩慢增加,在一定時間内逐漸增加到門檻值上限,給系統一個預熱的時間,避免冷系統被壓垮。

解釋:預設 coldFactor為3,即請求 QPS 從 threshold / 3 開始,經過預熱時長逐漸升到設定的QPS門檻值。具體可以看官網。

案例:門檻值為10 ,并且預熱時長設定為5秒

系統初始化的門檻值 為 10 / 3約等于3,即門檻值開始為3;然後過了5秒後門檻值才慢慢升高恢複到10.

具體實作可以看源碼 WarmUpController

試驗: 設定WarmUp , QPS,門檻值為10.預熱時長為5,

​ 操作:快速點選,開始還有失敗,5秒後便不再失敗,因為門檻值升高了。

應用場景:秒殺系統在開始的瞬間,會有很多流量,可能把系統打崩,預熱可以保護系統,慢慢提高到設定的門檻值。

排隊等待

勻速排隊,讓請求以均勻的速度通過,門檻值類型必須設定成QPS,否則無效。

可以設定單機門檻值1 表示 每秒1次請求,超過的話就排隊等待,逾時時間設定為20000,表示等待的逾時時間為20000毫米。

勻速排隊會嚴格控制請求通過的間隔時間,讓請求以均勻的速度通過,對應的是漏桶算法。

這種方式主要用來處理間隔性突發的流量,如消息隊列。

降級規則

降級簡介

官網:熔斷降級: https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7

Sentinel背景點選降級規則可進行設定

基本介紹:Sentinel熔斷降級會在調用鍊路中某個資源出現不穩定狀态時(例如調用逾時或異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其他資源而導緻級聯錯誤。

當資源被降級後,在接下來的降級時間視窗之内,對該資源的調用都自動熔斷(預設行為是抛出DegradException).

Sentinel 的斷路器是沒有半開狀态的 **

​ 半開的狀态系統去檢測是否請求有異常,沒有異常就關閉斷路器恢複使用,有異常則繼續打開斷路器不可用,具體可以參考Hystrix

​ 複習Hystrix

RT

平均響應時間,秒級

a)平均響應時間, 超出門檻值 且 b)在時間視窗内通過的請求 >= 5, 兩個條件同時滿足後 --> 觸發發降級

視窗期過後 —> 關閉斷路器

RT最大4900 (更大的需要通過 -Dcsp.sentinel.statistic.max.rt=xxxx才能生效)

測試

代碼:

@GetMapping("/testD")
public String testD() {
    //暫停幾秒鐘線程
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("testD 測試RT");
    return "------testD";
}
           

降級設定:

​ 點選鍊路 的 降級

jemeter壓力測試

​ 啟動方式: jemeter解壓檔案夾bin目錄,點選jemeter指令檔案

​ 設定為10 個,永遠, 這樣每次都是一秒進來10個,超過預設的5個

異常比例 (秒級)

是什麼:QPS >=5 且異常比例 (秒級統計)超過門檻值時,觸發降級;時間視窗結束後,關閉降級

​ 詳細介紹: 每秒請求量 >= 5,并且每秒異常總數占通過量的比值超過門檻值 之後,資源進入降級狀态,即在接下來的時間視窗之内,對這個方法的調用都會自動地傳回。異常比率的門檻值範圍是[1.0,1.0]代表0%~100%。

/testD裡加個

這樣就會報錯。

設定 在降級規則中設定 /testD 異常比例,為0.2 時間視窗為 3秒

jemeter測試: 發現被降級

手速快也可以實作測試,手速慢,次數不夠,會報錯,但是手速快,次數多後,就不再報錯,而是blocked,表明被降級

異常數 (分鐘級)

異常數 (分鐘統計) 超過門檻值時,觸發降級;時間視窗結束後,關閉降級

解釋:當資源近1分鐘的異常數目超過門檻值之後會進行熔斷。注意由于統計時間視窗是分鐘級别的,若timeWindow 小于60s,則結束熔斷狀态後仍可能再肌肉怒熔斷狀态。

流程: 異常數(分鐘統計)超過門檻值 -> 觸發降級(斷路器打開) -> 時間視窗結束 -> 關閉降級

異常數按分鐘統計

時間視窗設定一定要大于60s

測試:

@GetMapping("/testE")
    public String testE() {
        log.info("testE 測試異常數");
        int age = 10/0;
        return "-------testE 測試異常數";
    }
           

設定異常數為 5 ,時間視窗為70 ,浏覽器連續通路 5次後,第6次blocked,注意時間視窗

熱點key限流

基本介紹 何為熱點?熱點即經常通路的資料。很多時候我們希望統計某個熱點資料中通路頻次最高的 Top K 資料,并對其通路進行限制。比如:

  • 商品 ID 為參數,統計一段時間内最常購買的商品 ID 并進行限制
  • 使用者 ID 為參數,針對一段時間内頻繁通路的使用者 ID 進行限制

熱點參數限流會統計傳入參數中的熱點參數,并根據配置的限流門檻值與模式,對包含熱點參數的資源調用進行限流。熱點參數限流可以看做是一種特殊的流量控制,僅對包含熱點參數的資源調用生效。

官網 https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81

承上啟下複習start

兜底方法: 分為系統預設和客戶自定義,兩種

之前的case,限流出問題後,都是用sentinel系統預設的提示: Blocked by Sentinel (flow limiting)

我們能不能自定義?類似hystrix, 謀個方法出問題了,就找對應的兜底降級方法?

結論:從HystrixCommand 到 @SentinelResource

代碼

@GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")//違反了sentinel規則就找那個兜底方法
//前面的value是辨別,後面的是指定自定義的兜底方法
    public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                             @RequestParam(value = "p2",required = false) String p2) {
        //int age = 10/0;
        return "------testHotKey";
    }

    //兜底方法
    public String deal_testHotKey (String p1, String p2, BlockException exception){
        return "------deal_testHotKey,o(╥﹏╥)o";
    }
           

犯的兩個錯誤: 1,兜底方法裡是BlockException,不是BlockedException,

​ 2. 兜底方法參數個數應該包含被限流的方法的參數,即:被限流的方法裡有p1,p2兩個參數,兜底方法裡也該有,之前寫的隻有p1,導緻抛出一個關于參數的 FlowException

配置

點選 熱點規則 ,點選新增熱點限流規則 ,進行設定

資源名 :@SentinelResource注解裡的value,這裡是testHotKey,

設定 參數索引 0,單機門檻值 1,統計視窗時長 1

​ 意思: 假如 請求帶的參數下标 為0,在統計時長1秒内,通路次數超過1次,即觸發限流,直接調用blockHandler方法。

假如不設定blockHandler,會直接将ErrorPage放到前台。

測試

先做個測試,索引數設定為0, 帶p1 參數,可以觸發,同時帶p1和p2,也觸發,但是隻帶p2,狂點都不會觸發。因為沒限制第二個參數

這裡涉及參數例外項,在進階選項裡

參數例外項

特殊情況:我們期望當p1是某個特殊值的時候,它的限流和平時不一樣

​ 特例:假如當p1的值等于5時,它的門檻值可以達到200

設定:在參數例外項裡,參數類型 java.lang.String ,參數值為5,門檻值為200,就是指針對p1的參數,當值為5時,門檻值為200

其他

可以試着在程式裡加個 int age = 10/0;

發現,blockHandler不會去兜底,

因為@SentinelResource處理的是Sentinel控制台配置的違規情況,有blockHandler方法配置的兜底處理,

對于程式運作時産生的RuntimeException,@SentinelResource是不管的。

fallback才管運作時産生的異常

系統規則

是什麼:系統自适應限流

Sentinel 系統自适應限流從整體次元對應用入口流量進行控制,結合應用的 Load、CPU 使用率、總體平均 RT、入口 QPS 和并發線程數等幾個次元的監控名額,通過自适應的流控政策,讓系統的入口流量和系統的負載達到一個平衡,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。

前面的限流都是對 某個鍊路 例如 /testA, 甚至某個方法進行限流,但是,現在想在整個系統入口層面進行限制

設定: 系統規則

有 門檻值類型 LOAD 自适應: 超過估算的系統容量 觸發,設定參考值一般是CPU cores * 2.5

RT

線程數

入口 QPS

CPU使用率

示例:配置全局QPS 門檻值 設定為2 ,然後發現各個入口,都可以觸發,因為它是系統層面的

@SentinelResource配置

按資源名稱限流 + 後續處理

a.啟動Nacos成功

b.啟動Sentinel成功

c.Module 引入cloud-api-commons通用包

<!--引入自定義的api通用包,可以使用Payment支付Entity-->
<dependency>
    <groupId>com.huawei.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
</dependency>
           

d. 建立controller類來測試

@RestController
public class RateLimitController {

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handleException")
    public CommonResult byResource() {
        return new CommonResult(200,"按資源名稱限流測試OK",new Payment(2021L,"serial001"));
    }

    public CommonResult handleException(BlockException exception) {
        return new CommonResult(444,exception.getClass().getCanonicalName() + "\t 服務不可用");
    }

}
           

e.配置流控規則

​ 配置步驟:

​ 點選流控規則

​ 圖形配置和代碼關系

​ 點選 簇點鍊路,點選清單視圖,對 byResource 點選 流控 設定 (這裡沒有左斜杠)

​ 單機門檻值 設定 為 1;

​ 表示1秒鐘内查詢次數大于1,就跑到我們自定義處,限流

​ 與上面的流控相比,這裡的流控有處理方法

按照Url位址限流+後續處理

通過通路的URL來限流,會傳回Sentinel自帶預設的限流處理資訊

業務類ReteLimitController資訊

@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl() {
    return new CommonResult(200,"按url限流測試OK",new Payment(2021L,"serial002"));
}
           

通路一次:http://localhost:8401/rateLimit/byUrl 成功

Sentinel控制台設定

​ 對 /rateLimit/byUrl 進行流控 (這裡有左斜杠)

​ QPS 門檻值設定為 1

​ 測試發現,會直接 進入Blocked by Sentinel (flow limiting),這裡走預設的處理方法

​ 對/byResource和/rateLimit/byUrl 進行流控,不會進入blockHandler,

但是 對 byUrl 和 byResource 進行流控,也就是 @SentinelResource進行流控,則會進入blockHandler,可見,隻有對 @SentinelResource 的value進行流控,才生效,而對這個 路徑位址url進行設定,則會直接進入 系統預設的 處理方法

上述兜底方案面臨的問題

問題:1.系統預設的,沒有展現我們自己的業務要求

2. 依照現在的條件,自定義的處理方法和業務代碼耦合到一塊,不直覺
		3. 每個業務方法都添加一個兜底的,那代碼膨脹加劇
		4. 全局統一的處理方法沒有展現。
           

客戶自定義限流處理邏輯

建立CustomerBlockHandler類用于自定義限流處理邏輯 (解決耦合問題)

自定義限流處理類

​ a)建立CoustomerBlockHandler

public class CustomerBlockHandler {
    public static CommonResult handlerException(BlockException exception) {
        return new CommonResult(444,"按客戶自定義,global handlerException----1");
    }
    public static CommonResult handlerException2(BlockException exception) {
        return new CommonResult(404,"按客戶自定義,global handlerException ------2");
    }
}
           

RateLimitController

@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class,  //指定客戶自定義的兜底方法流淚
        blockHandler = "handlerException2")    //指定具體的客戶自定義方法
public CommonResult customerBlockHandler() {
    return new CommonResult(200,"按客戶自定義",new Payment(2021L,"serial003"));
}
           

啟動微服務後先調用一次 ,傳回前台

​ {“code”:200,“message”:“按客戶自定義”,“data”:{“id”:2021,“serial”:“serial003”}}

Sentinel控制台配置

​ 這裡是對 customerBlockHandler 進行流控,沒有左斜杠

​ 門檻值設定為 1 進行測試

測試後我們自定義的出來了

​ {“code”:404,“message”:“按客戶自定義,global handlerException ------2”,“data”:null}

進一步說明

​ blockHandlerClass 配置的為 處理類的資訊,blockHandler 配置的是 具體的某個處理方法的名字

其實也可以用代碼來解決,但是侵入性強,這裡不介紹

更多注解屬性說明

所有的代碼 用 try catch 包括進去

Sentinel主要有三個核心Api : SphU 定義資源

​ Tracer定義統計

​ ContextUtil定義了上下文

服務熔斷功能

​ sentinel整合ribbon + openFeign + fallback

Sentinel服務熔斷Ribbon環境

Ribbon系列

​ a)啟動nacos和sentinel

​ b)提供者9003/9004

​ 建立cloudalibaba-provider-payment9003/9004兩個一樣的做法

​ POM

<dependencies>

    <!--SpringCloud alibaba nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!--SpringBoot整合Web元件-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

</dependencies>
           

YML

server:
  port: 9003

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置nacos位址

management:
  endpoints:
    web:
      exposure:
        include: '*'
           

主啟動類

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9003.class, args);
    }
}
           

業務類

@RestController
public class PaymentController {

    @Value("${server.port}")
    private String serverPort;

    public static HashMap<Long, Payment> hashMap = new HashMap<>();

    static {
        hashMap.put(1L, new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2L, new Payment(2L,"BBA8C133bc2742d8848569891ac32182"));
        hashMap.put(3L, new Payment(3L,"6ua8c1e3bc2743d8848569891xt92183"));
    }

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult(200, "from mysql,serverPort: " + serverPort, payment);
        return result;
    }

}
           

測試位址: http://localhost:9003/paymentSQL/1

c)消費者84

​ 建立cloudalibaba-consumer-nacos-order84

​ POM

<dependencies>

        <!--SpringCloud alibaba nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--SpringCloud alibaba sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.huawei.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--SpringBoot整合Web元件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>
           

​ YML

server:
  port: 84

spring:
  application:
    name: nacow-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        # 配置Sentinel dashboard位址
    dashboard: localhost:8080
        # 預設8719端口,假如被占用會自動從8719開始掃描,直至找到未被占用的端口
    port: 8719

# 消費者将要去通路的微服務名稱(注冊成功進nacos的微服務提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider
           

​ 主啟動

@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84 {
    public static void main(String[] args) {
        SpringApplication.run(OrderNacosMain84.class, args);
    }

}
           

​ 業務類

config:

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

}
           

上面的都和83差不多,代碼比較常見

下面的是重點:

​ CircleBreakerController

@RestController
@Slf4j
public class CircleBreakerController {

    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback") //沒有配置
    public CommonResult<Payment> fallback(@PathVariable Long id) {

        CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);

        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法參數異常。。。。");
        }else if ( result.getData() == null ) {
            throw new NullPointerException("NullPointerException,該ID沒有對應記錄,空指針異常");
        }

        return result;
    }

}
           

後續操作:

修改後請重新開機微服務:對@SentinelResource注解内屬性有時效性

目的: fallback管運作異常

​ blockHandler管配置違規

Sentile服務熔斷無配置

接上面:進行測試,

測試位址: http://localhost:84/consumer/fallback/1

結果,出現輪詢了

{"code":200,"message":"from mysql,serverPort: 9003","data":{"id":1,"serial":"28a8c1e3bc2742d8848569891fb42181"}}
           

9003和9004實作了輪詢

登入Sentinel ,登入Nacos,都成功了,都能發現服務。

測試:當 發送請求帶的參數 變成 4 ,即 http://localhost:84/consumer/fallback/4

按照 if (id == 4) {

throw new IllegalArgumentException(“IllegalArgumentException,非法參數異常。。。。”);

這段代碼,系統抛出了異常

而且傳回前台的是error頁面,非常不友好,現在,我們需要進行配置,

服務熔斷隻配置fallback

根據上面的進行改進,需要對報的錯誤進行處理了,不讓傳回error頁面

@RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback") //沒有配置
    @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback隻負責業務異常
    public CommonResult<Payment> fallback(@PathVariable Long id) {

        CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);

        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法參數異常。。。。");
        }else if ( result.getData() == null ) {
            throw new NullPointerException("NullPointerException,該ID沒有對應記錄,空指針異常");
        }

        return result;
    }

    //本例是fallback
    public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(444,"兜底異常handlerFallback,exception内容 " + e.getMessage(),payment);
    }
           

啟動後再次通路:http://localhost:84/consumer/fallback/4

傳回結果

{"code":444,"message":"兜底異常handlerFallback,exception内容 IllegalArgumentException,非法參數異常。。。。","data":{"id":4,"serial":"null"}}
           

這樣就有個兜底方法了,比報錯誤頁面好多了

服務熔斷隻配置blockHandler

代碼改進

@RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback") //沒有配置
//    @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback隻負責業務異常
    @SentinelResource(value = "fallback",blockHandler = "blockHandler")//blockHandler隻負責sentinel隻負責sentinel控制台配置違規
    public CommonResult<Payment> fallback(@PathVariable Long id) {

        CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);

        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法參數異常。。。。");
        }else if ( result.getData() == null ) {
            throw new NullPointerException("NullPointerException,該ID沒有對應記錄,空指針異常");
        }

        return result;
    }

    //本例是blockHandler
    public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,無此流水:blockException " + blockException.getMessage(),payment);
    }
           

由于配置了sentinel,是以需要sentinel重新配置,對fallback進行配置,門檻值為2,然後,第一次通路

http://localhost:84/consumer/fallback/4

依然報錯,快速點選兩次達到門檻值,然後點選,發現

{"code":445,"message":"blockHandler-sentinel限流,無此流水:blockException null","data":{"id":4,"serial":"null"}}
           

思考這個和上個的配置的差別,效果有啥不同

第二個隻負責業務的異常,業務出異常就觸發,第三個則是當達到sentinel配置的違規時才觸發。

fallback和blockHandler都配置

@RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback") //沒有配置
//    @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback隻負責業務異常
//    @SentinelResource(value = "fallback",blockHandler = "blockHandler")//blockHandler隻負責sentinel隻負責sentinel控制台配置違規
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler") //兩個都配,誰生效?
    public CommonResult<Payment> fallback(@PathVariable Long id) {

        CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);

        if (id == 4) {
            throw new IllegalArgumentException("IllegalArgumentException,非法參數異常。。。。");
        }else if ( result.getData() == null ) {
            throw new NullPointerException("NullPointerException,該ID沒有對應記錄,空指針異常");
        }

        return result;
    }

    //本例是blockHandler
    public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,無此流水:blockException " + blockException.getMessage(),payment);
    }

    //本例是fallback
    public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(444,"兜底異常handlerFallback,exception内容 " + e.getMessage(),payment);
    }
           

sentinel不配置時候,觸發的是fallback

{"code":444,"message":"兜底異常handlerFallback,exception内容 IllegalArgumentException,非法參數異常。。。。","data":{"id":4,"serial":"null"}}
           

對sentinel進行配置,配置sentinel 中fallback 門檻值為2,點選一次還是上面那個fallback,但是達到門檻值後,就觸發blockHandler,即,被限流降級而抛出了 BlockException時,隻會進入blockHandler的處理邏輯。

{"code":445,"message":"blockHandler-sentinel限流,無此流水:blockException null","data":{"id":4,"serial":"null"}}
           

服務熔斷exceptionsToIgnore

異常忽略

在上面例子上加以改造

再次通路:http://localhost:84/consumer/fallback/4

看看會不會觸發fallback

結果:直接将非法參數異常列印給了前台,也就是說,配置Ignore後,再次出現這個異常,将不會走fallback

服務熔斷OpenFeign

​ Feign系列

修改84子產品: 84消費者調用提供者9003 Feign元件一般是消費側

POM

​ 添加:OpenFeign依賴 8401的OpenFeign依賴一開始建立時就加了

<!--SpringCloud openfeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
           

YML

# 激活Sentinel對Feign的支援
feign:
  sentinel:
    enabled: true
           

業務類

​ 帶@FeignClient注解的業務接口

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
    @GetMapping(value= "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
           

​ fallback = PaymentFallbackService.class

@Component
public class PaymentFallbackService implements PaymentService {

    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(444,"服務降級傳回,----PaymentFallbackService",new Payment(id,"serialError"));
    }
}
           

​ Controller

//----------OpenFeign
    @Resource
    private PaymentService paymentService;

    @GetMapping(value = "/consumer/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        return paymentService.paymentSQL(id);
    }
           

主啟動

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class OrderNacosMain84 {
    public static void main(String[] args) {
        SpringApplication.run(OrderNacosMain84.class, args);
    }

}
           

開始測試時,一直都是直接服務降級,後來發現是主啟動類沒有加 @EnableFeignClients

測試:http://localhost:84/consumer/paymentSQL/1

{"code":200,"message":"from mysql,serverPort: 9003","data":{"id":1,"serial":"28a8c1e3bc2742d8848569891fb42181"}}
           

測試84調用9003,此時故意關閉9003微服務提供者,看84消費側自動降級

{"code":444,"message":"服務降級傳回,----PaymentFallbackService","data":{"id":1,"serial":"serialError"}}
           

服務降級,可以和Hystrix比較下

規則持久化

之前的遺留問題,8401每次重新開機都要重新設定Sentinel流控規則

臨時/持久?

生産環境需要将配置規則持久化

怎麼玩?

将限流配置規則持久化進Nacos儲存,隻要重新整理8401某個rest位址,sentinel控制台的流控規則就能看到,隻要Nacos裡面的配置不删除,針對8401上sentinel的流控規則持續有效

為啥可以,因為Nacos = eureka + config + bus

開始操作

a) 修改cloudalibaba-sentinel-service8401

b)POM

<!--SpringCloud alibaba sentinel - datasource-nacos  後續做持久化用到-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
           

C)YML

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服務注冊中心位址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard位址
        dashboard: localhost:8080
        #預設8719端口,假如被占用會自動從8719開始依次+1掃描,直至找到未被占用的端口
        port: 8719
        # 以下為該功能需要的
        datasource:
          ds1:
            nacos:
              server-addr: localhost:8848
              dataId: cloudalibaba-sentinel-service #就是application.name
              groupId: DEFAULT_GROUP
              data-type: json
              rule-type: flow
           

d) 添加Nacos業務規則配置

​ 配置清單 點 + 号 :

​ 内容解析

​ Data Id : 就寫dataId

​ 配置格式選 json

[
    {
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]
           

解釋:

​ resource: 資源名稱;

​ limitApp: 來源應用;

​ grade: 門檻值類型,0表示線程數,1表示QPS;

​ count: 單機門檻值;

​ stratgy: 流控模式,0表示直接,1表示關聯,2表示鍊路;

​ controlBehavior: 流控效果,0表示快速失敗,1表示Warm Up, 2表示排隊等待;

​ clusterMode: 是否叢集。

​ 釋出儲存

​ 在sentinel的cloudalibaba-sentinel-service 即8401 裡設定/rateLimit/byUrl的流控

e) 啟動8401後重新整理sentinel發現業務規則有了

快速通路測試接口 :localhost:8401/rateLimit/byUrl 發現被限流

測試能否将設定的規則持久化: 停止8401再看sentinel 沒有

重新啟動8401,再次通路localhost:8401/rateLimit/byUrl ,再看看sentinel,發現規則仍在,證明規則被持久化了。