微服務保護——Sentinel
介紹Sentinel
1.背景
Sentinel是阿里巴巴開源的一款微服務流量控制組件,官網地址:https://sentinelguard.io/zh-cn/index.html
Sentinel 具有以下特征:
?豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的范圍)、訊息削峰填谷、集群流量控制、實時熔斷下游不可用應用等,
?完備的實時監控:Sentinel 同時提供實時的監控功能,您可以在控制臺中看到接入應用的單臺機器秒級資料,甚至 500 臺以下規模的集群的匯總運行情況,
?廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合,您只需要引入相應的依賴并進行簡單的配置即可快速地接入 Sentinel,
?完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展介面,您可以通過實作擴展介面來快速地定制邏輯,例如定制規則管理、適配動態資料源等,
2. 服務保護技術對比
在SpringCloud當中支持多種服務保護技術:
- Netfix Hystrix
- Sentinel
- Resilience4J
早期比較流行的是Hystrix框架,但目前國內實用最廣泛的還是阿里巴巴的Sentinel框架,這里我們做下對比:
| Sentinel | Hystrix | |
|---|---|---|
| 隔離策略 | 信號量隔離 | 執行緒池隔離/信號量隔離 |
| 熔斷降級策略 | 基于慢呼叫比例或例外比例 | 基于失敗比率 |
| 實時指標實作 | 滑動視窗 | 滑動視窗(基于 RxJava) |
| 規則配置 | 支持多種資料源 | 支持多種資料源 |
| 擴展性 | 多個擴展點 | 插件的形式 |
| 基于注解的支持 | 支持 | 支持 |
| 限流 | 基于 QPS,支持基于呼叫關系的限流 | 有限的支持 |
| 流量整形 | 支持慢啟動、勻速排隊模式 | 不支持 |
| 系統自適應保護 | 支持 | 不支持 |
| 控制臺 | 開箱即用,可配置規則、查看秒級監控、機器發現等 | 不完善 |
| 常見框架的適配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
3.安裝Sentinel
1)下載
sentinel官方提供了UI控制臺,方便我們對系統做限流設定,大家可以在GitHub下載,
課前資料也提供了下載好的jar包:

2)運行
將jar包放到任意非中文目錄,執行命令:
java -jar sentinel-dashboard-1.8.1.jar
如果要修改Sentinel的默認埠、賬戶、密碼,可以通過下列配置:
| 配置項 | 默認值 | 說明 |
|---|---|---|
| server.port | 8080 | 服務埠 |
| sentinel.dashboard.auth.username | sentinel | 默認用戶名 |
| sentinel.dashboard.auth.password | sentinel | 默認密碼 |
例如,修改埠:
java -Dserver.port=8090 -jar sentinel-dashboard-1.8.1.jar
3)訪問
訪問http://localhost:8080頁面,就可以看到sentinel的控制臺了:需要輸入賬號和密碼,默認都是:sentinel

登錄后,發現一片空白,什么都沒有:這是因為我們還沒有與微服務整合,

微服務整合Sentinel
四步驟:
0. 啟動Nacos
進入到nacos的bin檔案夾中cmd:startup.cmd -m standalone
1. 依賴
在指定微服務中匯入該依賴
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2. yaml組態檔
修改application.yaml檔案,添加下面內容:
server:
port: 8088 #微服務地址
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 #sentinel控制臺訪問地址
3. 訪問任意介面
打開瀏覽器,訪問任意介面 如:http://localhost:8088/order/101,這樣才能觸發sentinel的監控,
然后再訪問sentinel的控制臺,查看效果:

FeignClient整合Sentinel
整合后撰寫失敗降級邏輯:就是請求失敗后不是直接回傳一個例外而是回傳一個空物件(保證用戶體驗)
SpringCloud中,微服務呼叫都是通過Feign來實作的,因此做客戶端保護必須整合Feign和Sentinel,
1. 修改組態檔
保證已經有了Feign和sentinel依賴
修改OrderService的application.yml檔案,開啟Feign的Sentinel功能:
feign:
sentinel:
enabled: true # 開啟feign對sentinel的支持
2. 撰寫請求失敗降級邏輯
業務失敗后,不能直接報錯,而應該回傳用戶一個友好提示或者默認結果,這個就是失敗降級邏輯,
給FeignClient撰寫請求失敗后的降級邏輯
①方式一:FallbackClass,無法對遠程呼叫的例外做處理【不推薦】
②方式二:FallbackFactory,可以對遠程呼叫的例外做處理【推薦】
這里我們演示方式二的請求失敗降級處理,
步驟一:在feing-api專案中定義類,實作FallbackFactory:

代碼:
package cn.itcast.feign.clients.fallback;
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() { //前提是必須有UserClient類和findById方法
@Override
public User findById(Long id) {
log.error("查詢用戶例外", throwable);
return new User();
}
};
}
}
步驟二:在feing-api專案中的DefaultFeignConfiguration類中將UserClientFallbackFactory注冊為一個Bean:
記得配置類需要@Component
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
步驟三:在feing-api專案中的UserClient介面中使用UserClientFallbackFactory:
import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
import cn.itcast.feign.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "https://www.cnblogs.com/buchizicai/p/userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
重啟后,訪問一次訂單查詢業務,然后查看sentinel控制臺,可以看到新的簇點鏈路:

雪崩問題
白話:雪崩問題是微服務之間相互呼叫,因為呼叫鏈中的一個服務故障,引起整個鏈路都無法訪問的情況,
服務A和其他服務需要服務D回應,但此時服務D故障了,服務A無法接收到結果,由于服務器支持的執行緒和并發數有限,請求一致阻塞會導致服務器資源耗盡,從而導致依賴于當前服務的其它服務隨著時間的推移,最終也都會變的不可用,形成級聯失敗,雪崩就發生了
與服務D執行緒有關的服務雪崩:
服務D故障——> 服務A等有關服務阻塞
與服務D執行緒無關服務雪崩:
服務D故障——> 大量請求阻塞 ——> 服務器資源耗盡 ——>其他服務變得不可用 ——> 級聯失敗(雪崩)
如果服務提供者I發生了故障,當前的應用的部分業務因為依賴于服務I,因此也會被阻塞,此時,其它不依賴于服務I的業務似乎不受影響,但是,依賴服務I的業務請求被阻塞,用戶不會得到回應,則tomcat的這個執行緒不會釋放,于是越來越多的用戶請求到來,越來越多的執行緒會阻塞,服務器支持的執行緒和并發數有限,請求一直阻塞,會導致服務器資源耗盡,從而導致所有其它服務都不可用,那么當前服務也就不可用了,那么,依賴于當前服務的其它服務隨著時間的推移,最終也都會變的不可用,形成級聯失敗,雪崩就發生了

解決方案
限流是對服務的保護,避免因瞬間高并發流量而導致服務故障,進而避免雪崩,是一種預防措施,
超時處理、執行緒隔離、降級熔斷是在部分服務故障時,將故障控制在一定范圍,避免雪崩,是一種補救措施,
1. 預防措施
1.1 限流
流量控制:限制業務訪問的QPS,避免服務因流量的突增而故障,

2. 補救措施
2.1 超時處理
超時處理:設定超時時間,請求超過一定時間沒有回應就回傳錯誤資訊,不會無休止等待

2.2 艙壁模式
艙壁模式來源于船艙的設計:船艙都會被隔板分離為多個獨立空間,當船體破損時,只會導致部分空間進入,將故障控制在一定范圍內,避免整個船體都被淹沒,

我們可以限定每個業務能使用的執行緒數,避免耗盡整個tomcat的資源,因此也叫執行緒隔離,

2.3 斷路器
斷路器模式:由斷路器統計業務執行的例外比例,如果超出閾值則會熔斷該業務,攔截訪問該業務的一切請求,
斷路器會統計訪問某個服務的請求數量,例外比例:

當發現訪問服務D的請求例外比例過高時,認為服務D有導致雪崩的風險,會攔截訪問服務D的一切請求,形成熔斷:

限流:流量控制
1. 簇點鏈路
當請求進入微服務時,首先會訪問DispatcherServlet,然后進入Controller、Service、Mapper,這樣的一個呼叫鏈就叫做簇點鏈路,簇點鏈路中被監控的每一個介面就是一個資源,
默認情況下sentinel會監控SpringMVC的每一個端點(Endpoint,也就是controller中的方法),因此SpringMVC的每一個端點(Endpoint)就是呼叫鏈路中的一個資源,
例如,我們剛才訪問的order-service中的OrderController中的端點:/order/

流控、熔斷等都是針對簇點鏈路中的資源來設定的,因此我們可以點擊對應資源后面的按鈕來設定規則:
- 流控:流量控制
- 降級:降級熔斷
- 熱點:熱點引數限流,是限流的一種
- 授權:請求的權限控制
2. 流控模式
QPS是每秒請求數
流控模式有哪些?
?直接:對當前資源限流
?關聯:高優先級資源觸發閾值,對低優先級資源限流,
?鏈路:閾值統計時,只統計從指定資源進入當前資源的請求,是對請求來源的限流
2.1 直接模式
直接模式:請求訪問介面在每秒內只能通過n個請求(n是單機閾值),其他請求會報錯429:被限流
配置規則:

2.2 關聯模式
需要對哪個介面限流就對哪個介面(端點)設定流控
使用場景:比如用戶支付時需要修改訂單狀態,同時用戶要查詢訂單,查詢和修改操作會爭搶資料庫鎖,產生競爭,業務需求是優先支付和更新訂單的業務,因此當修改訂單業務觸發閾值時,需要對查詢訂單業務限流,
滿足以下條件可以使用關聯模式:
- 兩個有競爭關系的資源
- 一個優先級較高,一個優先級較低
關聯模式:統計與當前資源相關的另一個資源(相不相關人為說了算),觸發閾值時,對當前資源限流
配置規則:

語法說明:當/write資源訪問量觸發閾值n時,就會對/read資源限流,避免影響/write資源,
舉例:
需求說明:
-
在OrderController新建兩個端點:/order/query和/order/update,無需實作業務
-
配置流控規則,當/order/ update資源被訪問的QPS超過5時,對/order/query請求限流
1)定義/order/query端點,模擬訂單查詢
@GetMapping("/query")
public String queryOrder() {
return "查詢訂單成功";
}
2)定義/order/update端點,模擬訂單更新
@GetMapping("/update")
public String updateOrder() {
return "更新訂單成功";
}
重啟微服務并訪問介面,查看sentinel控制臺的簇點鏈路:

3)配置流控規則
對哪個端點限流,就點擊哪個端點后面的按鈕,我們是對訂單查詢/order/query限流,因此點擊它后面的按鈕:

在表單中填寫流控規則:

4)在Jmeter測驗
選擇《流控模式-關聯》:

可以看到1000個用戶,100秒,因此QPS為10,超過了我們設定的閾值:5
查看http請求:

請求的目標是/order/update,這樣這個斷點就會觸發閾值,
但限流的目標是/order/query,我們在瀏覽器訪問,可以發現:確實被限流了,

2.3鏈路模式
鏈路模式:只針對從指定鏈路訪問到本資源的請求做統計,判斷是否超過閾值,
配置示例:
例如有兩條請求鏈路:
- /test1 --> /common
- /test2 --> /common
test1和test2是介面,common一般是方法,判斷從這個介面訪問這個方法的單機閾值是否到達QPS,超過則限制
如果只希望統計從/test2進入到/common的請求,則可以這樣配置:

舉例:
需求:有查詢訂單和創建訂單業務,兩者都需要查詢商品,針對從查詢訂單進入到查詢商品的請求統計,并設定限流,
步驟:
-
在OrderService中添加一個queryGoods方法,不用實作業務
-
在OrderController中,改造/order/query端點,呼叫OrderService中的queryGoods方法
-
在OrderController中添加一個/order/save的端點,呼叫OrderService的queryGoods方法
-
給queryGoods設定限流規則,從/order/query進入queryGoods的方法限制QPS必須小于2
實作:
1)添加查詢商品方法
在order-service服務中,給OrderService類添加一個queryGoods方法:
public void queryGoods(){
System.err.println("查詢商品");
}
2)查詢訂單時,查詢商品
在order-service的OrderController中,修改/order/query端點的業務邏輯:
@GetMapping("/query")
public String queryOrder() {
// 查詢商品
orderService.queryGoods();
// 查詢訂單
System.out.println("查詢訂單");
return "查詢訂單成功";
}
3)新增訂單,查詢商品
在order-service的OrderController中,修改/order/save端點,模擬新增訂單:
@GetMapping("/save")
public String saveOrder() {
// 查詢商品
orderService.queryGoods();
// 查詢訂單
System.err.println("新增訂單");
return "新增訂單成功";
}
4)給查詢商品添加資源標記
默認情況下,OrderService中的方法是不被Sentinel監控的,需要我們自己通過注解來標記要監控的方法,
給OrderService的queryGoods方法添加@SentinelResource注解:
@SentinelResource("goods")
public void queryGoods(){
System.err.println("查詢商品");
}
鏈路模式中,是對不同來源的兩個鏈路做監控,但是sentinel默認會給進入SpringMVC的所有請求設定同一個root資源,會導致鏈路模式失效,
我們需要關閉這種對SpringMVC的資源聚合,修改order-service服務的application.yml檔案:
spring:
cloud:
sentinel:
web-context-unify: false # 關閉context整合
重啟服務,訪問/order/query和/order/save,可以查看到sentinel的簇點鏈路規則中,出現了新的資源:

5)添加流控規則
點擊goods資源后面的流控按鈕,在彈出的表單中填寫下面資訊:只統計從/order/query進入/goods的資源,QPS閾值為2,超出則被限流,

6)Jmeter測驗
選擇《流控模式-鏈路》:

可以看到這里200個用戶,50秒內發完,QPS為4,超過了我們設定的閾值2
一個http請求是訪問/order/save:

運行的結果:完全不受影響,

另一個是訪問/order/query:

運行結果:每次只有2個通過,

3. 流控效果
快速失敗:QPS超過閾值時,拒絕新的請求
warm up: QPS超過閾值時,拒絕新的請求;QPS閾值是逐漸提升的,可以避免冷啟動時高并發導致服務宕機,
排隊等待:請求會進入佇列,按照閾值允許的時間間隔依次執行請求;如果請求預期等待時長大于超時時間,直接拒絕
在流控的高級選項中,還有一個流控效果選項:

流控效果是指請求達到流控閾值時應該采取的措施,包括三種:
-
快速失敗:達到閾值后,新的請求會被立即拒絕并拋出FlowException例外,是默認的處理方式,
-
warm up:預熱模式,對超出閾值的請求同樣是拒絕并拋出例外,但這種模式閾值會動態變化,從一個較小值逐漸增加到最大閾值,
-
排隊等待(勻速器):讓所有的請求按照先后次序排隊執行,兩個請求的間隔不能小于指定時長
【勻速排隊,讓請求以勻速的速度通過,閾值型別必須設定為QPS,否則無效】
3.1 快速失敗
達到閾值后,新的請求會被立即拒絕并拋出FlowException例外,是默認的處理方式,
3.2 warm up
閾值一般是一個微服務能承擔的最大QPS,但是一個服務剛剛啟動時,一切資源尚未初始化(冷啟動),如果直接將QPS跑到最大值,可能導致服務瞬間宕機,
warm up也叫預熱模式,是應對服務冷啟動的一種方案,請求閾值初始值是 maxThreshold / coldFactor,持續指定時長后,逐漸提高到maxThreshold值,而coldFactor的默認值是3
例如,我設定QPS的maxThreshold為10,預熱時間為5秒,那么初始閾值就是 10 / 3 ,也就是3,然后在5秒后逐漸增長到10.

舉例:
需求:給/order/{orderId}這個資源設定限流,最大QPS為10,利用warm up效果,預熱時長為5秒
1)配置流控規則:

2)Jmeter測驗
選擇《流控效果,warm up》:

QPS為10.
剛剛啟動時,大部分請求失敗,成功的只有3個,說明QPS被限定在3:

隨著時間推移,成功比例越來越高:

到Sentinel控制臺查看實時監控:

一段時間后:

3.3 排隊等待
當請求超過QPS閾值時,快速失敗和warm up 會拒絕新的請求并拋出例外,
而排隊等待則是讓所有請求進入一個佇列中,然后按照閾值允許的時間間隔依次執行,后來的請求必須等待前面執行完成,如果請求預期的等待時間超出最大時長,則會被拒絕,
作業原理
例如:QPS = 5,意味著每200ms處理一個佇列中的請求;timeout = 2000,意味著預期等待時長超過2000ms的請求會被拒絕并拋出例外,
那什么叫做預期等待時長呢?
比如現在一下子同時來了12 個請求,因為每200ms執行一個請求,那么:
- 第6個請求的預期等待時長 = 200 * (6 - 1) = 1000ms
- 第12個請求的預期等待時長 = 200 * (12-1) = 2200ms
現在,第1秒同時接收到10個請求,但第2秒只有1個請求,此時QPS的曲線這樣的:

如果使用佇列模式做流控,所有進入的請求都要排隊,以固定的200ms的間隔執行,QPS會變的很平滑:

平滑的QPS曲線,對于服務器來說是更友好的,
舉例:
需求:給/order/{orderId}這個資源設定限流,最大QPS為10,利用排隊的流控效果,超時時長設定為5s
1)添加流控規則

2)Jmeter測驗
選擇《流控效果,佇列》:

QPS為15,已經超過了我們設定的10,
如果是之前的 快速失敗、warmup模式,超出的請求應該會直接報錯,
但是我們看看佇列模式的運行結果:

全部都通過了,
再去sentinel查看實時監控的QPS曲線:

QPS非常平滑,一致保持在10,但是超出的請求沒有被拒絕,而是放入佇列,因此回應時間(等待時間)會越來越長,
當佇列滿了以后,才會有部分請求失敗:

限流 :熱點引數限流
之前的限流是統計訪問某個資源的所有請求,判斷是否超過QPS閾值,而熱點引數限流是分別統計引數值相同的請求,判斷是否超過QPS閾值,
1. 全域引數限流
例如,一個根據id查詢商品的介面:

訪問/goods/{id}的請求中,id引數值會有變化,熱點引數限流會根據引數值分別統計QPS,統計結果:

當id=1的請求觸發閾值被限流時,id值不為1的請求不受影響,
配置示例:

解釋:對hot這個資源的0號引數(第一個引數)做統計,每1秒相同引數值的請求數不能超過5
2. 熱點引數限流
剛才的配置中,對查詢商品這個介面的所有商品一視同仁,QPS都限定為5.
而在實際開發中,可能部分商品是熱點商品,例如秒殺商品,我們希望這部分商品的QPS限制與其它商品不一樣,高一些,那就需要配置熱點引數限流的高級選項了:

結合上一個配置,這里的含義是對0號的long型別引數限流,每1秒相同引數的QPS不能超過5,有兩個例外:
?如果引數值是100,則每1秒允許的QPS為10
?如果引數值是101,則每1秒允許的QPS為15
案例
案例需求:給/order/{orderId}這個資源添加熱點引數限流,規則如下:
?默認的熱點引數規則是每1秒請求量不超過2
?給102這個引數設定例外:每1秒請求量不超過4
?給103這個引數設定例外:每1秒請求量不超過10
注意事項:熱點引數限流對默認的SpringMVC資源無效,需要利用@SentinelResource注解標記資源
1)標記資源
給order-service中的OrderController中的/order/{orderId}資源添加注解:

2)熱點引數限流規則
訪問該介面,可以看到我們標記的hot資源出現了:

這里不要點擊hot后面的按鈕,頁面有BUG
點擊左側選單中熱點規則選單:

點擊新增,填寫表單:

3)Jmeter測驗
選擇《熱點引數限流 QPS1》:

這里發起請求的QPS為5.
包含3個http請求:
普通引數,QPS閾值為2

運行結果:

例外項,QPS閾值為4

運行結果:

例外項,QPS閾值為10

運行結果:

艙壁模式:執行緒隔離
執行緒隔離建議設定監控介面里的遠程呼叫,因為一旦發生熔斷和隔離是不允許外界訪問該介面,監控遠程呼叫是因為遠程呼叫使用的feign-api模塊對遠程呼叫介面方法寫了發生熔斷和隔離時回傳空物件,如果監控外部介面,一旦發生隔離則直接報錯,阻止用戶訪問介面并不會回傳空物件(因為該介面方法沒寫發生熔斷和隔離時回傳空物件),
1. 執行緒隔離的兩種方式
執行緒隔離有兩種方式實作:
區別:
信號量——高扇出(高并發) 執行緒池——底扇出(請求量小)
特點:
信號量隔離——基于計數器模式,簡單,開銷小
執行緒池隔離是——基于執行緒池模式,有額外開銷,但隔離控制更強
-
執行緒池隔離
-
信號量隔離(Sentinel默認采用,選擇QPS)
如圖:

執行緒池隔離:給每個服務呼叫業務分配一個執行緒池,利用執行緒池本身實作隔離效果
信號量隔離:不創建執行緒池,而是計數器模式,記錄業務使用的執行緒數量,達到信號量上限時,禁止新的請求,
兩者的優缺點:

2. sentinel的執行緒隔離
用法說明:
在添加限流規則時,可以選擇兩種閾值型別:

-
QPS:就是每秒的請求數,在快速入門中已經演示過
-
執行緒數:是該資源能使用用的tomcat執行緒數的最大值,也就是通過限制執行緒數量,實作執行緒隔離(艙壁模式),
案例:
案例需求:給 order-service服務中的UserClient的查詢用戶介面設定流控規則,執行緒數不能超過 2,然后利用jemeter測驗,
1)配置隔離規則
選擇feign介面后面的流控按鈕:

填寫表單:

2)Jmeter測驗
選擇《閾值型別-執行緒數<2》:

一次發生10個請求,有較大概率并發執行緒數超過2,而超出的請求會走之前定義的失敗降級邏輯,
查看運行結果:

發現雖然結果都是通過了,不過部分請求得到的回應是降級回傳的null資訊,
路斷器:熔斷降級
熔斷建議設定監控介面里的遠程呼叫,因為一旦發生熔斷和隔離是不允許外界訪問該介面,監控遠程呼叫是因為遠程呼叫使用的feign-api模塊對遠程呼叫介面方法寫了發生熔斷和隔離時回傳空物件,如果監控外部介面,一旦發生熔斷則直接報錯,阻止用戶訪問介面并不會回傳空物件(因為該介面方法沒寫發生熔斷和隔離時回傳空物件),
熔斷降級是解決雪崩問題的重要手段,其思路是由斷路器統計服務呼叫的例外比例、慢請求比例,如果超出閾值則會熔斷該服務,即攔截訪問該服務的一切請求;而當服務恢復時,斷路器會放行訪問該服務的請求,
斷路器控制熔斷和放行是通過狀態機來完成的:

狀態機包括三個狀態:
- closed:關閉狀態,斷路器放行所有請求,并開始統計例外比例、慢請求比例,超過閾值則切換到open狀態
- open:打開狀態,服務呼叫被熔斷,訪問被熔斷服務的請求會被拒絕,快速失敗,直接走降級邏輯,Open狀態5秒后會進入half-open狀態
- half-open:半開狀態,放行一次請求,根據執行結果來判斷接下來的操作,
- 請求成功:則切換到closed狀態
- 請求失敗:則切換到open狀態
斷路器熔斷策略有三種:慢呼叫、例外比例、例外數
1. 熔斷策略一:慢呼叫
慢呼叫:業務的回應時長(RT)大于指定時長的請求認定為慢呼叫請求,在指定時間內,如果請求數量超過設定的最小數量,慢呼叫比例大于設定的閾值,則觸發熔斷,
例如:

解讀:RT超過500ms的呼叫是慢呼叫,統計最近10000ms內的請求,如果請求量超過10次,并且慢呼叫比例不低于0.5,則觸發熔斷,熔斷時長為5秒,然后進入half-open狀態,放行一次請求做測驗,
案例:
需求:給 UserClient的查詢用戶介面設定降級規則,慢呼叫的RT閾值為50ms,統計時間為1秒,最小請求數量為5,失敗閾值比例為0.4,熔斷時長為5
1)設定慢呼叫
修改user-service中的/user/{id}這個介面的業務,通過休眠模擬一個延遲時間:

此時,orderId=101的訂單,關聯的是id為1的用戶,呼叫時長為60ms:

orderId=102的訂單,關聯的是id為2的用戶,呼叫時長為非常短;

2)設定熔斷規則
下面,給feign介面設定降級規則:

規則:

超過50ms的請求都會被認為是慢請求
3)測驗
在瀏覽器訪問:http://localhost:8088/order/101,快速重繪5次后,可以發現:觸發了熔斷,請求時長縮短至5ms,快速失敗了,并且走降級邏輯,回傳的null
這里呼叫order/101是因為,這個介面里面會呼叫user/101,所以還是會觸發前面設定的熔斷規則

在瀏覽器訪問:http://localhost:8088/order/102,竟然也被熔斷了:

2. 熔斷策略二和三:例外比例、例外數
例外比例或例外數:統計指定時間內的呼叫,如果呼叫次數超過指定請求數,并且出現例外的比例達到設定的比例閾值(或超過指定例外數),則觸發熔斷,
例如,一個例外比例設定:

解讀:統計最近1000ms內的請求,如果請求量超過10次,并且例外比例不低于0.4,則觸發熔斷,
一個例外數設定:

解讀:統計最近1000ms內的請求,如果請求量超過10次,并且例外比例不低于2次,則觸發熔斷,
案例
需求:給 UserClient的查詢用戶介面設定降級規則,統計時間為1秒,最小請求數量為5,失敗閾值比例為0.4,熔斷時長為5s
1)設定例外請求
首先,修改user-service中的/user/{id}這個介面的業務,手動拋出例外,以觸發例外比例的熔斷:也就是說,id 為 2時,就會觸發例外

2)設定熔斷規則
下面,給feign介面設定降級規則:

規則:在5次請求中,只要例外比例超過0.4,也就是有2次以上的例外,就會觸發熔斷,

3)測驗
在瀏覽器快速訪問:http://localhost:8088/order/102,快速重繪5次,觸發熔斷:

此時,我們去訪問本來應該正常的103:

授權
授權規則可以對請求方來源做判斷和控制,(通過判斷請求方的請求頭是否攜帶指定的引數來判斷)
1. sentinel授權介紹
授權規則可以對呼叫方的來源做控制,有白名單和黑名單兩種方式,
-
白名單:來源(origin)在白名單內的呼叫者允許訪問
-
黑名單:來源(origin)在黑名單內的呼叫者不允許訪問
點擊左側選單的授權,可以看到授權規則:

-
資源名:就是受保護的資源,例如/order/
-
流控應用:是來源者的名單,
- 如果是勾選白名單,則名單中的來源被許可訪問,
- 如果是勾選黑名單,則名單中的來源被禁止訪問,
比如:

我們允許請求從gateway到order-service,不允許瀏覽器訪問order-service,那么白名單中就要填寫網關的來源名稱(origin),
如何得到origin呢?
Sentinel是通過RequestOriginParser這個介面的parseOrigin來獲取請求的來源的,
public interface RequestOriginParser {
/**
* 從請求request物件中獲取origin,獲取方式自定義
*/
String parseOrigin(HttpServletRequest request);
}
這個方法的作用就是從request物件中,獲取請求者的origin值并回傳,
默認情況下,sentinel不管請求者從哪里來,回傳值永遠是default,也就是說一切請求的來源都被認為是一樣的值default,
因此,我們需要自定義這個介面的實作,讓不同的請求,回傳不同的origin,
2. sentinel設定授權
2.1 給網關添加請求頭
既然獲取請求origin的方式是從reques-header中獲取origin值,我們必須讓所有從gateway路由到微服務的請求都帶上origin頭,
這個需要利用之前學習的一個GatewayFilter來實作,AddRequestHeaderGatewayFilter,
修改gateway服務中的application.yml,添加一個defaultFilter:
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=origin,gateway #逗號前是key,后面是value
routes:
# ...略
這樣,從gateway路由的所有請求都會帶上origin頭,值為gateway,而從其它地方到達微服務的請求則沒有這個頭,
2.2 獲取請求的origin
例如order-service服務中,我們定義一個RequestOriginParser的實作類:我們會嘗試從request-header中獲取origin值,
package cn.itcast.order.sentinel;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 1.獲取請求頭
String origin = request.getHeader("origin");
// 2.非空判斷
if (StringUtils.isEmpty(origin)) {
origin = "blank";
}
return origin;
}
}
2.3 sentinel操作
我們添加一個授權規則,放行origin值為gateway的請求,

配置如下:

現在,我們直接跳過網關,訪問order-service服務:

通過網關訪問:

自定義例外結果
默認情況下,發生限流、降級、授權攔截時,都會拋出例外到呼叫方,例外結果都是flow limmiting(限流),這樣不夠友好,無法得知是限流還是降級還是授權攔截,
1.例外型別
而如果要自定義例外時的回傳結果,需要實作BlockExceptionHandler介面:
public interface BlockExceptionHandler {
/**
* 處理請求被限流、降級、授權攔截時拋出的例外:BlockException
*/
void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}
這個方法有三個引數:
- HttpServletRequest request:request物件
- HttpServletResponse response:response物件
- BlockException e:被sentinel攔截時拋出的例外
這里的BlockException包含多個不同的子類:
| 例外 | 說明 |
|---|---|
| FlowException | 限流例外 |
| ParamFlowException | 熱點引數限流的例外 |
| DegradeException | 降級例外 |
| AuthorityException | 授權規則例外 |
| SystemBlockException | 系統規則例外 |
2.自定義例外處理
下面,我們就在order-service定義一個自定義例外處理類:
package cn.itcast.order.sentinel;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = "未知例外";
int status = 429;
if (e instanceof FlowException) {
msg = "請求被限流了";
} else if (e instanceof ParamFlowException) {
msg = "請求被熱點引數限流";
} else if (e instanceof DegradeException) {
msg = "請求被降級了";
} else if (e instanceof AuthorityException) {
msg = "沒有權限訪問";
status = 401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
}
}
重啟測驗,在不同場景下,會回傳不同的例外訊息.
限流:

授權攔截時:

sentinel規則持久化
sentinel的所有規則都是記憶體存盤,重啟后所有規則都會丟失,在生產環境下,我們必須確保這些規則的持久化,避免丟失,
1.規則管理模式
規則是否能持久化,取決于規則管理模式,sentinel支持三種規則管理模式:
- 原始模式:Sentinel的默認模式,將規則保存在記憶體,重啟服務會丟失,
- pull模式(存盤各服務器本地,一定時間內會輪詢檢查規則并更新)
- push模式(存盤在nacos注冊中心)【推薦】
2. pull模式
pull模式:控制臺將配置的規則推送到Sentinel客戶端,而客戶端會將配置規則保存在本地檔案或資料庫中,以后會定時去本地檔案或資料庫中查詢,更新本地規則,

3. push模式
push模式:控制臺將配置規則推送到遠程配置中心,例如Nacos,Sentinel客戶端監聽Nacos,獲取配置變更的推送訊息,完成本地配置更新,

4. 實作push模式
4.1 引入依賴
在order-service中引入sentinel監聽nacos的依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
4.2 配置nacos地址
在order-service中的application.yml檔案配置nacos地址及監聽的配置資訊:
spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: localhost:8848 # nacos地址
dataId: orderservice-flow-rules #該組態檔的名稱
groupId: SENTINEL_GROUP #該組態檔所在組的名稱
rule-type: flow # 還可以是:degrade、authority、param-flow
flow:
nacos:
server-addr: localhost:8848 # nacos地址
dataId: orderservice-degrade-rules #該組態檔的名稱
groupId: SENTINEL_GROUP #該組態檔所在組的名稱
rule-type: degrade # 還可以是:degrade、authority、param-flow
#... 可以多個flow根據需求設定
4.3 修改sentinel原始碼
一般不這樣修改太麻煩了,直接去網上找別人改好的
SentinelDashboard默認不支持nacos的持久化,需要修改原始碼,
4.3.1 解壓
解壓課前資料中的sentinel原始碼包:

然后并用IDEA打開這個專案,結構如下:

4.3.2 修改nacos依賴
在sentinel-dashboard原始碼的pom檔案中,nacos的依賴默認的scope是test,只能在測驗時使用,這里要去除:

將sentinel-datasource-nacos依賴的scope去掉:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
4.3.3 添加nacos支持
在sentinel-dashboard的test包下,已經撰寫了對nacos的支持,我們需要將其拷貝到main下,

4.3.4 修改nacos地址
然后,還需要修改測驗代碼中的NacosConfig類:

修改其中的nacos地址,讓其讀取application.properties中的配置:

在sentinel-dashboard的application.properties中添加nacos地址配置:
nacos.addr=localhost:8848
4.3.5 配置nacos資料源
另外,還需要修改com.alibaba.csp.sentinel.dashboard.controller.v2包下的FlowControllerV2類:

讓我們添加的Nacos資料源生效:

4.3.6 修改前端頁面
接下來,還要修改前端頁面,添加一個支持nacos的選單,
修改src/main/webapp/resources/app/scripts/directives/sidebar/目錄下的sidebar.html檔案:

將其中的這部分注釋打開:

修改其中的文本:

4.3.7 重新編譯、打包專案
運行IDEA中的maven插件,編譯和打包修改好的Sentinel-Dashboard:

4.3.8 啟動
啟動方式跟官方一樣:
java -jar sentinel-dashboard.jar
如果要修改nacos地址,需要添加引數:
java -jar -Dnacos.addr=localhost:8848 sentinel-dashboard.jar
本文來自博客園,作者:不吃紫菜,遵循CC 4.0 BY-SA著作權協議,轉載請附上原文出處鏈接:https://www.cnblogs.com/buchizicai/p/17093746.html及本宣告,
本文著作權歸作者所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/543715.html
標籤:Java
上一篇:JAVA - - - String, StringBuffer,StringBuilder的區別
下一篇:Maven依賴管理
