文章目錄
- Spring Cloud Alibaba(03)——Sentinel實作熔斷與限流
-
- 1、Sentinel概述
- 2、Sentinel下載下傳安裝運作
- 3、初始化示範工程
- 4、流控規則
-
- 4.1、流控規則簡介
- 4.2、流控模式
-
- 4.2.1、直接
- 4.2.2、關聯
- 4.2.3、鍊路
- 4.3、流控效果
-
- 4.3.1、快速失敗
- 4.2.2、WarmUp
- 4.2.3、勻速排隊
- 5、降級規則
-
- 5.1、降級規則簡介
- 5.2、降級政策實戰
-
- 5.2.1、慢調用比例
- 5.2.2、異常比例
- 5.2.3、異常數
- 6、熱點規則
- 7、系統規則
- 8、@SentinelSource配置
-
- 8.1、按資源名稱限流
- 8.2、按url位址限流
- 8.3、客戶自定義限流處理邏輯
- 9、服務熔斷功能
-
- 1、整合Ribbon
- 2、整合OpenFeign
- 3、熔斷架構比較
- 10、規則持久化
Spring Cloud Alibaba(03)——Sentinel實作熔斷與限流
1、Sentinel概述
Sentinel 是什麼?
随着微服務的流行,服務和服務之間的穩定性變得越來越重要。Sentinel 是面向分布式服務架構的輕量級流量控制元件,主要以流量為切入點,從限流、流量整形、熔斷降級、系統負載保護等多個次元來幫助您保障微服務的穩定性。
Sentinel 具有以下特征:
- 豐富的應用場景:Sentinel 承接了阿裡巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的範圍)、消息削峰填谷、叢集流量控制、實時熔斷下遊不可用應用等。
- 完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制台中看到接入應用的單台機器秒級資料,甚至 500 台以下規模的叢集的彙總運作情況。
- 廣泛的開源生态:Sentinel 提供開箱即用的與其它開源架構/庫的整合子產品,例如與 Spring Cloud、Dubbo、gRPC 的整合。您隻需要引入相應的依賴并進行簡單的配置即可快速地接入 Sentinel。
- 完善的 SPI 擴充點:Sentinel 提供簡單易用、完善的 SPI 擴充接口。您可以通過實作擴充接口來快速地定制邏輯。例如定制規則管理、适配動态資料源等。
Sentinel 的主要特性:
以上内容摘自Sentinel GitHub官網:https://github.com/alibaba/Sentinel
Sentinel v1.8 中文文檔手冊
2、Sentinel下載下傳安裝運作
1、下載下傳安裝
下載下傳位址:https://github.com/alibaba/Sentinel/releases/tag
2、運作
Sentinel 分為兩個部分:
- 核心庫(Java 用戶端)不依賴任何架構/庫,能夠運作于所有 Java 運作時環境,同時對 Dubbo / Spring Cloud 等架構也有較好的支援。
- 控制台(Dashboard)基于 Spring Boot 開發,打包後可以直接運作,不需要額外的 Tomcat 等應用容器。
運作jar包:java -jar sentinel-dashboard-1.8.0.jar
通路請求:http://localhost:8080
使用者名和密碼都為sentinel,點選登入
Sentinel下載下傳安裝運作成功。
3、初始化示範工程
1、啟動nacos
2、建立子產品
- 建立cloudalibaba-sentinel-service8401子產品
- 導入pom依賴
<dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--持久化--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <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> </dependencies>
- 編寫yml配置檔案
server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 #nacos服務中心位址 sentinel: transport: dashboard: localhost:8080 #配置Sentinel dashboard位址,通過8080監控8401 port: 8719 #預設8719端口,假如被占用會自動從8719開始依次+1掃描,直至找到未被占用的端口 management: endpoints: web: exposure: include: '*'
- 建立主啟動類
@SpringBootApplication @EnableDiscoveryClient public class MainApp8401 { public static void main(String[] args) { SpringApplication.run(MainApp8401.class,args); } }
- 編寫業務
@RestController public class FlowLimitController { @GetMapping(value = "/testA") public String testA(){ return "------testA"; } @GetMapping(value = "/testB") public String testB(){ return "------testB"; } }
- 啟動 sentinel-dashboard
java -jar sentinel-dashboard-1.8.0.jar
- 啟動 cloudalibaba-sentinel-service8401子產品
-
因為Sentinel采用的是懶加載,是以需要我們通路一次請求,sentinel-dashboard 才能監控到8401服務
http://localhost:8401/testA
http://localhost:8401/testA
- 再重新整理sentinel-dashboard 界面
4、流控規則
4.1、流控規則簡介
流量控制(flow control),其原理是監控應用流量的 QPS 或并發線程數等名額,當達到指定的門檻值時對流量進行控制,以避免被瞬時的流量高峰沖垮,進而保障應用的高可用性。
一條限流規則主要由下面幾個因素組成,我們可以組合這些元素來實作不同的限流效果:
- 資源名:唯一名稱,預設請求路徑
- 針對來源: Sentinel可以針對調用者進行限流,填寫微服務名,預設default(不區分來源)門檻值類型/單機門檻值:
- QPS(每秒鐘的請求數量)∶當調用該api的QPS達到門檻值的時候,進行限流。
- 線程數:當調用該api的線程數達到門檻值的時候,進行限流
- 是否叢集:不需要叢集
- 流控模式:
- 直接: api達到限流條件時,直接限流
- 關聯:當關聯的資源達到門檻值時,就限流自己
- 鍊路:隻記錄指定鍊路上的流量(指定資源從入口資源進來的流量,如果達到門檻值,就進行限流)【api級别的針對來源】
- 流控效果:
- 快速失敗:直接失敗,抛異常
- Warm Up:根據codeFactor (冷加載因子,預設3)的值,從門檻值/codeFactor,經過預熱時長,才達到設定的QPS門檻值
- 排隊等待:勻速排隊,讓請求以勻速的速度通過,門檻值類型必須設定為QPS,否則無效
4.2、流控模式
4.2.1、直接
QPS直接失敗
Sentinel的流控模式代表的流控的方式,預設【直接】;
上面的/testA接口的流控,QPS單機閥值為1,代表每秒請求不能超出1,要不然就做流控處理,處理方式直接調用失敗;
調用/testA,慢一點請求,正常傳回;快速請求幾次,超過閥值;接口傳回了Blocked by Sentinel (flow limiting),代表被限流了;
配置及說明:
測試:
線程數直接失敗
Sentinel 并發線程數限流不負責建立和管理線程池,而是簡單統計目前請求上下文的線程數目,如果超出門檻值,新的請求會被立即拒絕,效果類似于信号量隔離。
1s内超過一個線程通路,就會直接失敗。
測試:
給http://localhost:8401/testA請求加一秒延遲,然後開啟兩個頁面進行通路:
@GetMapping(value = "/testA")
public String testA(){
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "------testA";
}
4.2.2、關聯
當與資源A關聯的資源B達到門檻值,就限流資源A自己。比如在分布式系統中,支付服務與訂單服務關聯,這樣當支付服務太過頻繁時,訂單服務的請求就被限流,禁止送出訂單。
配置與說明:
測試:用jmeter高并發壓/testB接口
上圖設定意味着每秒發送5次請求,即QPS=5。超過流控門檻值。
jmeter啟動壓測,浏覽器通路 http://localhost:8401/testB,會發現資源 /testA 已經被限流:
4.2.3、鍊路
鍊路流控模式指的是,當從某個接口過來的資源達到限流條件時,開啟限流;它的功能有點類似于針對 來源配置項,差別在于:針對來源是針對上級微服務,而鍊路流控是針對上級接口,也就是說它的粒度 更細。
僅對同一個service的某個入口進行流控,其他入口不做控制。
4.3、流控效果
4.3.1、快速失敗
直接快速失敗方式是預設的流量控制方式,當QPS超過任意規則的門檻值後,新的請求就會被立即拒絕,拒絕方式為抛出FlowException。這種方式适用于對系統處理能力确切已知的情況下,比如通過壓測确定了系統的準确水位時。
4.2.2、WarmUp
Warm Up(
RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式,即預熱/冷啟動方式。當系統長期處于低水位的情況下,當流量突然增加時,直接把系統拉升到高水位可能瞬間把系統壓垮。通過"冷啟動",讓通過的流量緩慢增加,在一定時間内逐漸增加到門檻值上限,給冷系統一個預熱的時間,避免冷系統被壓垮。
根據codeFactor(預設3)的值,從(閥值/codeFactor)為初始閥值,經過預熱時長,才到達設定的QPS的閥值
上面是什麼意思呢?我們來舉個案例,閥值為10,預熱時長設定5秒。
上圖意思為:我希望資源/testA 最終一秒能承受10個QPS,就是單機門檻值為10,但最開始隻給資源/testA 承受10/3個QPS,就是初始門檻值約為3,五秒後,才讓門檻值慢慢升高到10。
測試:
通路請求:http://localhost:8401/testA,快速重新整理,一開始會出現報錯,但5秒後,就算快速重新整理也不會報錯了。
應用場景:
如:秒殺系統在開啟的瞬間,會有很多流量上來,很有可能把系統打死,預熱方式就是把為了保護系統,可以慢慢的把流量放進來,慢慢的把門檻值增長到設定的門檻值。
4.2.3、勻速排隊
勻速排隊,讓請求以均勻的速度通過,閥值類型必須設成QPS,否則無效。
這種方式主要用于處理間隔性突發的流量,例如消息隊列。想象一下這樣的場景,在某一秒有大量的請求到來,而接下來的幾秒則處于空閑狀态,我們希望系統能夠在接下來的空閑期間逐漸處理這些請求,而不是在第一秒直接拒絕多餘的請求。
注意:勻速排隊模式暫時不支援 QPS > 1000 的場景。
配置及說明:
每通路一次/testA,背景會輸出一次日志
@GetMapping(value = "/testA")
public String testA(){
log.info(Thread.currentThread().getName()+"....testB");
return "------testA";
}
測試:再次利用jmeter對http://localhost:8401/testA進行壓測:設定20個線程,每秒執行一次,無限循環
啟動jmeter,檢視背景輸出:
我們可以發現testA每隔1秒執行一次,這麼多的請求沒有被拒絕,而且進入的排隊。
排隊的應用場景是什麼呢?
比如有時候系統在某一個時刻會出現大流量,之後流量就恢複穩定,可以采用這種排隊模式,大流量來時可以讓流量請求先排隊,等恢複了在慢慢進行處理
5、降級規則
5.1、降級規則簡介
除了流量控制以外,對調用鍊路中不穩定的資源進行熔斷降級也是保障高可用的重要措施之一。由于調用關系的複雜性,如果調用鍊路中的某個資源不穩定,最終會導緻請求發生堆積。Sentinel 熔斷降級會在調用鍊路中某個資源出現不穩定狀态時(例如調用逾時或異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導緻級聯錯誤。當資源被降級後,在接下來的降級時間視窗之内,對該資源的調用都自動熔斷(預設行為是抛出
Sentinel在1.8.0版本對熔斷降級做了大的調整,可以定義任意時長的熔斷時間,引入了半開啟恢複支援。
降級政策
Sentinel 提供以下幾種熔斷政策:
- 慢調用比例 (
):選擇以慢調用比例作為門檻值,需要設定允許的慢調用 RT(即最大的響應時間),請求的響應時間大于該值則統計為慢調用。==當機關統計時長(SLOW_REQUEST_RATIO
)内請求數目大于設定的最小請求數目,并且慢調用的比例大于門檻值,則接下來的熔斷時長内請求會自動被熔斷。==經過熔斷時長後熔斷器會進入探測恢複狀态(HALF-OPEN 狀态),若接下來的一個請求響應時間小于設定的慢調用 RT 則結束熔斷,若大于設定的慢調用 RT 則會再次被熔斷。RT最大4900.statIntervalMs
- 異常比例 (
):==當機關統計時長(ERROR_RATIO
)内請求數目大于設定的最小請求數目,并且異常的比例大于門檻值,則接下來的熔斷時長内請求會自動被熔斷。==經過熔斷時長後熔斷器會進入探測恢複狀态(HALF-OPEN 狀态),若接下來的一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。異常比率的門檻值範圍是statIntervalMs
,代表 0% - 100%。[0.0, 1.0]
- 異常數 (
):當機關統計時長内的異常數目超過門檻值之後會自動進行熔斷。經過熔斷時長後熔斷器會進入探測恢複狀态(HALF-OPEN 狀态),若接下來的一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。ERROR_COUNT
5.2、降級政策實戰
5.2.1、慢調用比例
慢調用:指耗時大于門檻值RT的請求稱為慢調用,門檻值RT由使用者設定
最小請求數:允許通過的最小請求數量,在最小請求數量内不發生熔斷,由使用者設定
配置及說明:
上面配置說明:
當請求數大于最小請求數5,并且慢調用的比率大于比例門檻值0.6,則發生熔斷,熔斷時長1s。
當熔斷過了定義的熔斷時長,狀态由熔斷(OPEN)變為探測(HALFOPEN)。
如果接下來的一個請求最大響應時間小于最大RT200ms,說明慢調用已經恢複,結束熔斷,狀态由探測(HALF_OPEN)變更為關閉(CLOSED)
如果接下來的一個請求最大響應時間大于最大RT200ms,說明慢調用未恢複,繼續熔斷,熔斷時長保持一緻
測試
增加一個方法:該方法的響應時間為1s,大于200ms,是以調用該方法為慢調用
@GetMapping(value = "/testD")
public String testD(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("=====testD=====");
return "------testD";
}
使用jmeter壓測:配置1秒10個線程去通路 localhost:8401/testD,大于最小請求數
啟動jmeter測試計劃,可以看到 localhost:8401/testD已被熔斷:
5.2.2、異常比例
通過計算異常比例與設定門檻值對比的一種政策。
當資源的每秒請求數大于等于最小請求數,并且異常總數占通過量的比例超過比例門檻值時,資源進入降級狀态。
配置及說明
說明:
當請求數大于5,并且異常比例大于0.2時,發生熔斷,熔斷時長微
秒,就是後面2秒内服務不可使用。
當超過熔斷時長時,由熔斷(OPEN)轉為探測(HALFOPEN)
如果接下來的一個請求未發生錯誤,說明應用恢複,結束熔斷,狀态由探測(HALF_OPEN)變更為關閉(CLOSED)
如果接下來的一個請求繼續發生錯誤,說明應用未恢複,繼續熔斷,熔斷時長保持一緻
測試
增加一個方法:該方法發生運作時異常,
@GetMapping(value = "/testC")
public String testC(){
log.info("=====testC=====");
int age = 10/0;
return "------testC";
}
使用jmeter壓測:配置1秒10個線程去通路 localhost:8401/testC
啟動jmeter測試計劃,可以看到 localhost:8401/testC已被熔斷:
當我們關閉jmeter,單獨通路這個請求時,因為不滿足降級條件(請求數不滿足),是以不會使用降級政策,會直接跳轉到Error Page。
5.2.3、異常數
通過計算發生異常的請求數與設定門檻值對比的一種政策
當資源近1分鐘的異常數目超過門檻值(異常數)之後會進行服務降級。注意由于統計時間視窗是分鐘級别的,若熔斷時長小于60s,則結束熔斷狀态後仍可能再次進入熔斷狀态。
配置及說明:
說明:
近一分鐘内,當請求數大5并且異常數量大于5時觸發熔斷,熔斷時長為70s。
當超過熔斷時長時,由熔斷(OPEN)轉為探測(HALFOPEN)
如果接下來的一個請求未發生錯誤,說明應用恢複,結束熔斷,狀态由探測(HALF_OPEN)變更為關閉(CLOSED)
如果接下來的一個請求繼續發生錯誤,說明應用未恢複,繼續熔斷,熔斷時長保持一緻
測試
通路請求 localhost:8401/testC 5次,前五次為錯誤頁面,當第6次時,觸發熔斷。
6、熱點規則
何為熱點?熱點即經常通路的資料。很多時候我們希望統計某個熱點資料中通路頻次最高的 Top K 資料,并對其通路進行限制。比如:
- 商品 ID 為參數,統計一段時間内最常購買的商品 ID 并進行限制
- 使用者 ID 為參數,針對一段時間内頻繁通路的使用者 ID 進行限制
熱點參數限流會統計傳入參數中的熱點參數,并根據配置的限流門檻值與模式,對包含熱點參數的資源調用進行限流。熱點參數限流可以看做是一種特殊的流量控制,僅對包含熱點參數的資源調用生效。
Sentinel 利用 LRU 政策統計最近最常通路的熱點參數,結合令牌桶算法來進行參數級别的流控。熱點參數限流支援叢集模式。
添加方法:
對于 @SentinelResource 注解方式定義的資源,若注解作用的方法上有參數,Sentinel 會将它們作為參數傳入
@GetMapping(value = "/testE")
@SentinelResource(value = "testE",blockHandler = "deal_hotKey") //設定兜底的方法,當不符合熱點配置規則,就使用兜底的方法,當程式有運作時異常時,還是走運作時異常
public String hotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "=====testE=====";
}
public String deal_hotKey(String p1, String p2, BlockException exception){
return "=====執行了兜底方法;deal_hotKey=====";
}
配置及說明:
說明:當參數p1每秒通路超過3次,就對包含參數p1的資源進行限流,限流時間1s
測試:
隻攜帶p1參數進行測試
正常通路資源:http://localhost:8401/testE?p1=abc
當一秒内通路資源超過三次時:
攜帶p1和p2測試
正常通路資源:http://localhost:8401/testE?p1=abc&p2=def
當一秒内通路資源超過三次時:照樣執行了兜底的方法
隻攜帶p2參數進行測試,因為熱點配置規則裡對參數p2沒有做相關的配置,是以p2通路沒有限制。
參數例外項
參數例外項,可以針對指定的參數值單獨設定限流門檻值,不受前面
count
門檻值的限制。僅支援基本類型和字元串類型
規則及說明:
說明:當參數p1的每秒通路大于3時,就對包含參數p1的資源進行限流,限流時間1s,但是當參數p1的值為9537時,它的QPS門檻值可以為1000,之前設定的單機門檻值3無效。
測試:通路請求:http://localhost:8401/testE?p1=9527&p2=def 隻要QPS不到1000通路,資源可以正常通路。
7、系統規則
系統自适應限流
Sentinel 系統自适應限流從整體次元對應用入口流量進行控制,結合應用的 Load、CPU 使用率、總體平均 RT、入口 QPS 和并發線程數等幾個次元的監控名額,通過自适應的流控政策,讓系統的入口流量和系統的負載達到一個平衡,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。
系統保護規則
系統保護規則是從應用級别的入口流量進行控制,從單台機器的 load、CPU 使用率、平均 RT、入口 QPS 和并發線程數等幾個次元監控應用名額,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。
系統保護規則是應用整體次元的,而不是資源次元的,并且僅對入口流量生效。入口流量指的是進入應用的流量(
EntryType.IN
),比如 Web 服務或 Dubbo 服務端接收的請求,都屬于入口流量。
系統規則支援以下的模式:
- Load 自适應(僅對 Linux/Unix-like 機器生效):系統的 load1 作為啟發名額,進行自适應系統保護。當系統 load1 超過設定的啟發值,且系統目前的并發線程數超過估算的系統容量時才會觸發系統保護(BBR 階段)。系統容量由系統的
估算得出。設定參考值一般是maxQps * minRt
。CPU cores * 2.5
- CPU usage(1.5.0+ 版本):當系統 CPU 使用率超過門檻值即觸發系統保護(取值範圍 0.0-1.0),比較靈敏。
- 平均 RT:當單台機器上所有入口流量的平均 RT 達到門檻值即觸發系統保護,機關是毫秒。
- 并發線程數:當單台機器上所有入口流量的并發線程數達到門檻值即觸發系統保護。
- 入口 QPS:當單台機器上所有入口流量的 QPS 達到門檻值即觸發系統保護。
8、@SentinelSource配置
@SentinelResource用于定義資源,并提供可選的異常處理和 fallback 配置項。 @SentinelResource 注解包含以下屬性:
- value:資源名稱,必需項(不能為空)
- entryType:entry 類型,可選項(預設為 EntryType.OUT)
- blockHandler / blockHandlerClass: blockHandler對應處理 BlockException 的函數名稱,可選項。blockHandler 函數通路範圍需要是 public,傳回類型需要與原方法相比對,參數類型需要和原方法相比對并且最後加一個額外的參數,類型為 BlockException。blockHandler 函數預設需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 blockHandlerClass為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
- fallback / fallbackClass:fallback 函數名稱,可選項,用于在抛出異常的時候提供 fallback 處理邏輯。fallback 函數可以針對所有類型的異常(除了exceptionsToIgnore裡面排除掉的異常類型)進行處理。fallback 函數簽名和位置要求:
- 傳回值類型必須與原函數傳回值類型一緻;
- 方法參數清單需要和原函數一緻,或者可以額外多一個 Throwable 類型的參數用于接收對應的異常。
- fallback 函數預設需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 為對應的類的 Class對象,注意對應的函數必需為 static 函數,否則無法解析。
-
defaultFallback
(since 1.6.0):預設的 fallback 函數名稱,可選項,通常用于通用的 fallback 邏輯(即可以用于很多服務或方法)。預設 fallback 函數可以針對所有類型的異常(除了exceptionsToIgnore裡面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則隻有 fallback 會生效。defaultFallback 函數簽名要求:
- 傳回值類型必須與原函數傳回值類型一緻;
- 方法參數清單需要為空,或者可以額外多一個 Throwable 類型的參數用于接收對應的異常。
- defaultFallback 函數預設需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 為對應的類的 Class對象,注意對應的函數必需為 static 函數,否則無法解析。
- exceptionsToIgnore(since 1.6.0):用于指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣抛出。
注意:注解方式不支援 private 方法。
8.1、按資源名稱限流
1、在cloudalibaba-sentinel-service8401子產品中加入pom依賴:
<dependency>
<groupId>com.cheng.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2、增加一個controller
@RestController
public class RateLimitController {
@GetMapping(value = "/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource(){
return new CommonResult(200,"按資源名限流測試ok",new Payment(12L,"serial001"));
}
public CommonResult handleException(BlockException exception){
return new CommonResult(444,exception.getClass().getCanonicalName()+"被限流,服務不可用");
}
}
3、測試
使用預設的流控模式
通路請求:http://localhost:8401/byResource,一秒内通路一次
一秒内多次通路:服務限流,傳回自定義的限流資訊
8.2、按url位址限流
增加一個方法:
@GetMapping(value = "/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl(){
return new CommonResult(200,"按url限流測試ok",new Payment(2021L,"serial002"));
}
配置流控規則:
測試
通路請求:http://localhost:8401/rateLimit/byUrl,一秒内通路一次
一秒内多次通路,服務限流,傳回系統自帶的限流資訊
8.3、客戶自定義限流處理邏輯
建立CustomerBlockHandler類用于自定義限流處理邏輯
public class CustomerBlockHandler {
public static CommonResult handlerException1(BlockException exception){
return new CommonResult(444,"按客戶自定義 方案一");
}
public static CommonResult handlerException2(BlockException exception){
return new CommonResult(444,"按客戶自定義 方案二");
}
}
增加方法:
@GetMapping(value = "/rateLimit/customerBH")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handlerException2") //選擇CustomerBlockHandler類中的handlerException2方法做限流處理
public CommonResult customerBlockHandler(){
return new CommonResult(200,"客戶自定義測試ok",new Payment(2021L,"serial003"));
}
增加流控規則:
測試
通路請求:http://localhost:8401//rateLimit/customerBH,一秒内通路一次
一秒内多次通路,服務限流,傳回自定義的且自己選擇的限流資訊
9、服務熔斷功能
1、整合Ribbon
服務提供者9003和9004
1、建立兩個服務提供者子產品:
cloudalibaba-provide-payment9003
cloudalibaba-provide-payment9004
這兩個子產品除了端口号不同,其他内容一樣。
2、導入pom依賴
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.cheng.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<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>
</dependencies>
3、編寫yml配置檔案
server:
port: 9003 // 或9004
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 配置Nacos的位址
#暴露端點
management:
endpoints:
web:
exposure:
include: '*'
4、主啟動類
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class,args);
}
}
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9004.class,args);
}
}
5、編寫業務類
@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,"bba8c1e3bc2742d8848569891ac32182"));
hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
}
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Integer id){
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200, "from mysql serverport= " + serverPort, payment);
return result;
}
}
6、測試
啟動 cloudalibaba-provide-payment9003 和 cloudalibaba-provide-payment9004 子產品
通路請求:http://localhost:9003/paymentSQL/3
通路請求:http://localhost:9004/paymentSQL/1
服務消費者84
1、建立 cloudalibaba-consumer-nacos-order84 子產品
2、導入pom依賴
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.cheng.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、編寫yml配置檔案
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos注冊中心位址
sentinel:
transport:
dashboard: localhost:8080 #sentinel位址,監控84
port: 8719 #預設8719端口,假如被占用會自動從8719開始依次+1掃描,直至找到未被占用的端口
4、主啟動類
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84{
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
5、編寫配置類,開啟ribbon負載均衡
@Configuration
public class ApplicationContextConfig{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
@SentinelResource沒有屬性
6、編寫業務類
@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<Payment> 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;
}
測試:
通路請求:http://localhost:84/consumer/fallback/1,通路的是9003
再通路一次,通路的是9004
之後持續通路,将在9003和9004之間輪換,實作了ribbon輪詢的負載機制。
當我們請求攜帶的參數不是1,2,3時,将會出現對應的Error 頁面、
@SentinelResource 隻增加 fallback 屬性
給@SentinelResource增加fallback屬性,并增加處理異常的方法:
@RestController
@Slf4j
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
//fallback隻負責業務異常
@SentinelResource(value = "fallback",fallback = "handlerFallback")
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> 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);
}
}
測試fallback方法是否生效:
通路請求:http://localhost:84/consumer/fallback/4,
通路請求:http://localhost:84/consumer/fallback/6
這時沒有出現Error Page,傳回了我們自定義的異常資訊
@SentinelResource 隻增加 blockHandler 屬性
@SentinelResource隻增加blockHandler屬性,并增加blockHandler方法:
@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",blockHandler = "blockHandler") //blockHandler隻負責sentinel控制台配置違規
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> 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控制台配置降級規則:
在一分鐘内,如果請求數大于5并且異常數大于3,觸發服務降級。
測試
通路請求:http://localhost:84/consumer/fallback/4 和 http://localhost:84/consumer/fallback/6 各一次:
出現異常頁面
持續通路請求:http://localhost:84/consumer/fallback/4 和 http://localhost:84/consumer/fallback/6 ,超過三次後:
觸發降級規則,服務熔斷,傳回自定義的處理資訊。
@SentinelResource 同時增加 blockHandler 和 fallback 屬性
@SentinelResource 同時增加 blockHandler 和 fallback 屬性,并加上相應的處理方法:
@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",fallback = "handlerFallback",blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> 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);
}
//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 流控規則:
測試
一秒内通路一次請求:http://localhost:84/consumer/fallback/4 ,因為此時的QPS沒有大于1,是以沒有破壞sentinel的限流規則,是以是傳回fallback的異常資訊。
但當一秒内通路次數大于1時,就破壞了sentinel的限流規則,傳回的是blockHandler處理的異常資訊
總結:若blockHandler和fallback都進行了配置,則被限流降級而抛出BlockException時隻會進入blockHandler處理邏輯。
exceptionsToIgnore屬性
在@SentinelResource 注解中使用 exceptionsToIgnore 屬性:
加了exceptionsToIgnore後,IllegalArgumentException 異常将不會被fallback 方法處理,會直接傳回Error Page。
2、整合OpenFeign
1、在cloudalibaba-consumer-nacos-order84 子產品添加openfeign 的依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、在84子產品的yml配置檔案添加如下配置:
feign:
sentinel:
enabled: true #激活sentinel對feign的支援
3、主啟動類增加注解@EnableFeignClients
4、編寫feign接口
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
5、編寫全局降級方法
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444,"服務異常,被 PaymentFallbackService 降級處理",new Payment(id,"error"));
}
}
需要在@FeignClient中配置fallback。
6、在controller中用openfeign調用服務
@Resource
private PaymentService paymentService;
@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
return paymentService.paymentSQL(id);
}
7、測試
通路請求:http://localhost:84/consumer/paymentSQL/1
再通路一次
關閉9003和9004服務,再次通路請求:http://localhost:84/consumer/paymentSQL/1
被全局降級方法處理。
3、熔斷架構比較
10、規則持久化
在上面的案例中,如果服務關閉了,那麼服務對應的流控規則也會消失。每次重新開機服務,就需要重新配置規則,這顯然不符合真實的開發需要。是以我需要将配置規則持久化。
持久化方案:将限流配置規則持久化進Nacos儲存,隻要重新整理8401子產品某個rest位址,sentinel控制台的流控規則就能看到,隻要Nacos裡面的配置不删除,針對8401上sentinel上的流控規則持續有效。
1、8401子產品中添加pom依賴:
<!--持久化-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2、在yml配置檔案中添加nacos資料源配置
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服務中心位址
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard位址,通過8080監控8401
port: 8719 #預設8719端口,假如被占用會自動從8719開始依次+1掃描,直至找到未被占用的端口
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
management:
endpoints:
web:
exposure:
include: '*'
3、在nacos中添加業務規則配置
配置說明:
resource:資源名稱;
limitApp:來源應用;
grade:門檻值類型,0表示線程數,1表示QPS;
count:單機門檻值;
strategy:流控模式,o表示直接,1表示關聯,2表示鍊路;
controlBehavior:流控效果,0表示快速失敗,1表示Warm up,2表示排隊等待;
clusterMode:是否叢集。
4、測試
關閉8401服務,看sentinel中是否還有流控規則:
流控規則消失了。
再重新開機8401服務,此時sebtinel中沒有流控規則,
當我們通路一次http://localhost:8401/rateLimit/byUrl請求,sentinel就監控到/rateLimit/byUrl,流控規則就會自動出現了: