Sentinel 是面向分散式服務架構的高可用流量防護組件,主要以流量為切入點,從限流、流量整形、熔斷降級、系統負載保護、熱點防護等多個維度來幫助開發者保障微服務的穩定性。它可以是 Java 應用程式中的任何內容,例如,由應用程式提供的服務,或由應用程式調用的其它應用提供的服務,甚至可以是一段代碼。... ...
微服務保護——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當中支持多種服務保護技術:
早期比較流行的是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 = "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及本聲明。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。