SpringCloud(11)— 微服務保護(Sentinel)
一 認識Sentinel
1.雪崩問題及其解決方案
微服務調用鍊路中的某個服務出現問題,引起整個鍊路中所有的微服務都不可用,這就是我們常說的雪崩問題。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL3gTOllTZyQjY5gTZ1EmM4MjNxQjYxMGN0kzNxczNykzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
如何解決雪崩問題?
常見的解決方案有如下幾種:
- 逾時處理:設定逾時時間,請求超過一定時間沒有響應就傳回錯誤資訊,防止無休止等待。
- 艙壁模式:限定每個業務使用的線程數,避免耗盡整個web伺服器資源,是以也叫線程隔離。
- 熔斷降級:由斷路器統計業務失敗的異常比例,如果超出門檻值則會熔斷該業務,攔截通路該業務的一切請求
- 流量控制:限制通路業務的QPS(機關時間内的業務數量),避免服務因流量的突增而故障
2.微服務保護技術對比
sentinel目前是SpringBoot Alibaba的一個元件。
3.認識Sentinel
Sentinel是阿裡巴巴的一款微服務流量控制元件。
官方網址:home | Sentinel (sentinelguard.io)
Github位址:GitHub - alibaba/Sentinel: A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向雲原生微服務的高可用流控防護元件)
Sentinel的特征:
- 豐富的應用場景
- 完備的實時監控
- 廣泛的開源生态
- 完善的SPI擴充點
4.安裝和運作Sentinel
Sentinel控制台 的下載下傳位址:Releases · alibaba/Sentinel (github.com)
下載下傳下來之後是一個 jar 包,使用指令運作即可,筆者這裡下載下傳的是最新版1.8.6
# 直接運作,預設端口為8080
java -jar sentinel-dashboard-1.8.6.jar
# 指定部分參數運作,通過 -Dserver.port=xxxx 指定運作端口
java -Dserver.port=8080 -jar sentinel-dashboard-1.8.6.jar
# 其餘更多配置參數參考官網
運作之後,在浏覽器通路即可。預設的使用者名和密碼均為 sentinel
常用的參數配置項:
使用舉例:運作時在參數向前面加上“ -D ”,例如 -Dserver.port=xxxx 即可。
5.項目整合Sentinel
1.引入Sentinel 的依賴
<!--引入Sentinel依賴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.在配置檔案中配置Sentinel相關參數
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
3.通路微服務的任意端口,觸發 sentinel 監控
二 流量控制
1.限流基本用法
簇點鍊路:指的是項目内部的調用鍊路,鍊路中被監控的每個接口就是一個資源。
預設情況下,sentinel 會監控微服務中的每一個端點(endpoint),是以微服務中的每一個端點就是調用鍊路中的一個資源
流控,熔斷
等都是
針對簇點鍊路中的資源
來設定的,可以直接點選資源後邊的按鈕來設定規則
1.流控設定
點選資源後面的
流控
按鈕,便可以對資源進行流控設定
- 資源名稱:表示要流控的資源
- 針對來源:表示從什麼地方進來的請求需要進行流控,
表示一切對該資源的請求都要進行流控default
- 門檻值類型:
表示機關時間内的并發量,後邊的QPS
表示所選門檻值類型的上限。如果超出則會被攔截并且報錯單機門檻值
利用
jmeter
進行測試,會發現超出設定的
QPS
單機門檻值的請求将傳回被sentinel攔截的資訊
2.流控模式
現在來看看流控規則中的額進階設定
在流控規則界面打開
進階選線
按鈕,可以看到有很多
流控模式
和
流控效果
供我們選擇
其中
流控模式
中的三種模式代表的含義分别如下:
- 直接:統計目前資源的請求,出發門檻值時對目前資源直接限流,也是預設的模式
- 關聯:統計與目前資源關聯的另一個資源,觸發門檻值時,對目前資源限流
- 鍊路:統計從指定鍊路通路到本資源的請求,觸發門檻值時,對
限流指定鍊路
Sentinel中的流控設定,預設情況下使用的是直接模式
1.關聯模式
統計與目前資源關聯的另一個資源,觸發門檻值時,對目前資源限流
使用場景:比如使用者支付訂單的同時,要查詢訂單。查詢和修改操作會争搶資料庫資源,産生競争。是以當支付訂單狀态出發門檻值時,需要對查詢業務限流
以上示例,當
/user/{id}
的
QPS
達到門檻值時,将會對
/order/{id}
進行限流,避免影響到
/user/{id}
滿足一下條件時可以考慮使用關聯模式:
- 兩個有競争關系的資源
- 一個優先級高,一個優先級低
2.鍊路模式
統計從指定鍊路通路到本資源的請求,觸發門檻值時,對
指定鍊路
限流
例如有兩條請求鍊路,
-
->/test1
/common
-
->/test2
/common
如果隻希望統計從
/test1
進入
/common
的請求,則可以這樣配置:
這樣一來,當從
/test1
進入
/common
的
QPS
達到門檻值時,将對
/test1
進行限流
Sentinel
預設值标記
Controller
中的方法為資源,如果要标記其他方法,需要利用
@SentinelResource
注解
@SentinelResource("data")
public void queryData(){
System.out.println("查詢資料");
}
Sentinel
預設會将
Controller
方法做
context
整合,導緻鍊路模式的流控失效,需要修改項目配置檔案,添加配置。
spring:
cloud:
sentinel:
# 關閉context整合
web-context-unify: false
transport:
dashboard: localhost:8080
3.流控效果
流控效果是指請求達到流控門檻值時應采取的措施
Sentinel
提供了三種流控效果:
- 快速失敗:達到門檻值後,新的請求會被直接拒絕并抛出
異常,這是預設的處理方式FlowException
- Warm Up:預熱模式,對于超出門檻值的請求同樣是直接拒絕。但是這種門檻值會動态變化,從一個較小門檻值逐漸增加到最大門檻值
- 排隊等待:讓所有請求按照先後順序排隊執行,兩個請求的間隔不能小于指定時長
1.Warm Up
warm up
也叫預熱模式,是應對服務冷啟動的一種方案。請求門檻值初始值為
threshold / coldFactor
,持續指定時長後,逐漸提高到
threshold
值,而
coldFactor
的預設值為3。
預熱模式主要是防止冷啟動時過高并發進而導緻故障
2.排隊等待
當請求超過
QPS
值時,快速失敗和預熱模式都會抛出異常。而排隊等待則是讓所有請求進入一個隊列中,然後按照門檻值允許的時間間隔依次執行。後來的請求必須等待前面的請求執行完成,如果請求預期的等待時間超出最大時長,則請求會被拒絕。
4.熱點參數限流
之前的限流是統計通路某個資源的所有請求,判斷是否超過
QPS
門檻值,而熱點參數限流是分别統計
參數值相同
的請求,判斷是否超過
QPS
門檻值。
例如以下配置:
熱點資源 hot 的參數值為1的限流門檻值為4
熱點資源 hot 的參數值為2的限流門檻值為2
由此可見,熱點參數限流是一種更為細膩的限流方式,将限流規則降低到了參數級别上。
三 隔離和降級
限流是對微服務故障的一種預防措施,但是一旦服務已經出現了故障,則我們需要其他手段來防止出現級聯失敗甚至雪崩的問題。
而要将這些故障控制在一定範圍内,避免雪崩,就需要依賴于
線程隔離(艙壁模式)
和
熔斷降級
手段了。
不管是
線程隔離
還是
熔斷降級
,都是對用戶端(調用者 / 消費者)的保護
1.FeignClient整合Sentinel
SpringCloud
中,微服務調用是通過Feign來實作的,是以做用戶端保護必須整合
Feign
和
Sentinel
步驟1:修改消費者微服務的項目配置檔案,開啟Feign的Sentinel功能
feign:
sentinel:
# 開啟 Feign 的 Sentinel 功能
enabled: true
開啟以後,
Feign請求
将會作為一個資源存在于
Sentinel
中,這樣我們就可以對它進行
流控
等操作了
步驟2:給
FeignClient
編寫失敗後的降級邏輯
- 方式1:FallbackClass,無法對遠端調用的異常做處理
- 方式2:FallbackFactory,可以對遠端調用的異常做處理,一般使用這種
步驟3:編寫具體的降級邏輯
在
feign-api
項目中編寫 UserClientFallbackFactory
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<IUserClientFeign> {
@Override
public IUserClientFeign create(Throwable throwable) {
return new IUserClientFeign() {
@Override
public User findById(Integer id) {
log.error("查詢異常");
//未查詢到使用者時指定傳回内容,此處傳回空對象
return new User();
}
};
}
}
将其定義為
bean
@Configuration
public class FeignConfig {
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
}
在
@FeignClient
上指定
fallbackFactory
@FeignClient(value = "alibaba-user",fallbackFactory = UserClientFallbackFactory.class)
@RequestMapping("/user")
public interface IUserClientFeign {
/**
* 定義請求接口
* @param id userId
* @return 傳回對象
*/
@GetMapping("/{id}")
User findById(@PathVariable("id") Integer id);
}
2.線程隔離
線程隔離有兩種實作方式:
- 線程池隔離
- 信号量隔離(sentinel預設)
線程池隔離原理:程式啟動時預設建立指定數量的線程池用于被其他微服務使用。當線程池被全部占據時将不再接受通路。業務處理完時線程解除占用。
信号量隔離原理:采用信号量計數器的辦法,當計數器達到指定的數量則不再接受通路。即有新業務時信号量+1,業務處理完成時信号量-1。
在添加限流規則時,可以選擇兩種門檻值類型:
- QPS:每秒請求數
- 線程數:是該資源使用的
線程數的最大值,也就是通過限制線程數量,實作艙壁模式tomcat
3.熔斷降級
熔斷降級是解決雪崩問題的重要手段,其思路是由斷路器統計服務調用的異常比例,慢請求比例。如果超出門檻值則會熔斷該服務。即攔截該服務的一切請求;而當服務恢複時,斷路器也會放行該服務的請求。
以下是斷路器的工作流程
斷路器熔斷政策有三種:慢調用,異常比例,異常數。
1.慢調用
慢調用:業務的響應時間(RT)大于指定時長的請求認定為慢調用請求。在指定時間内請求數量超過設定的最小數量,慢調用比例大于設定的門檻值時觸發熔斷。
以上示例的含義:對于
/order/{id}
資源,響應時間大于
2000ms
時認為時慢調用。統計
1000ms
内的請求,如果請求量超過
5次
且慢調用比例超過
0.5
,則觸發熔斷,熔斷時長為
5s
,然後進入
Half-Open
狀态,放行一次請求做測試。
2.異常比例或異常數
異常比例或異常數:統計指定時間内的調用,如果調用次數超過指定請求數,并且
異常的比例
或
異常數
達到設定的門檻值,則觸發熔斷。
異常比例或異常數的熔斷規則與調用時長無關,隻關心是否抛出異常和抛出異常的比例
以上示例的含義:對于
/user/{id}
資源,統計
1000ms
内的請求,如果請求量超過
5次
且異常比例超過
0.5
,則觸發熔斷,熔斷時長為
5s
,然後進入
Half-Open
狀态,放行一次請求做測試。
四 授權規則
授權規則可以對調用方的來源做控制,有白名單和黑名單兩種方式
- 白名單:來源(origin)在白名單内的調用者允許通路
- 黑名單:來源(origin)在黑名單内的調用者不允許通路
1.請求來源
Sentinel是通過RequestOriginParse這個接口的parseOrigin來擷取請求來源的
2.授權實作
在微服務中實作
RequestOriginParser
接口,這裡與統一網關約定使用
origin
作為請求頭參數名。
@Component
public class HeaderOriginParse implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// 1.擷取請求頭
String origin=httpServletRequest.getHeader("origin");
if(StringUtils.isEmpty(origin)){
origin="blank";
}
return origin;
}
}
在統一網關服務的配置檔案中添加
origin
請求頭參數,值為
alibaba-gateway
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=origin,alibaba-gateway
通過以上設定,可以保證隻要是來源于
統一網關服務
的請求,請求頭參數
origin
的值均為
alibaba-gateway
最後,在授權規則中填入來源名稱,設定白名單即可
添加了授權規則之後,對于
/order/{id}
的通路就必須通過統一網管來通路。
注意:請求來源參數(origin)的參數名和參數值切勿對外暴露,防止别人僞造請求頭
3.自定義異常攔截請求
預設情況下,發生限流,降級,熔斷時,都會抛出異常到調用方。如果要實作自定義異常時的傳回結果,需要實作
BlockExceptionHandler
接口。
BlockException
包含很多子類,分别對應不同的場景
以下是微服務調用時被
Sentinel
攔截的簡單示例:
@Component
public class SentinelBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
String msg = "未知異常";
if (e instanceof FlowException) {
msg = "請求被限流";
} else if (e instanceof DegradeException) {
msg = "請求被降級";
} else if (e instanceof ParamFlowException) {
msg = "熱點參數被限流";
}else if (e instanceof AuthorityException) {
msg = "授權規則異常";
}
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpServletResponse.getWriter().println("{\n" +
" \"status\": 429,\n" +
" \"msg\":\""+msg+"\"\n" +
"}");
}
}
以下是傳回示例:
傳回的Json格式自定義,此處隻做示範,不深究
{
"status": 429,
"msg": "授權規則異常"
}
五 規則持久化
在配置了
Sentinel
的各種規則後,重新開機服務會導緻規則丢失。
這是因為
Sentinel
将規則儲存在記憶體中,重新開機微服務規則自然會丢失。
1.規則管理模式
Sentinel
的控制台規則管理有三種模式:
- 原始模式:
的預設模式,将規則儲存在記憶體,重新開機服務則丢失Sentinel
- pull模式:儲存在本地或資料庫,定時去讀取
- push模式:儲存在
,監聽變更實時更新Nacos
1.Pull模式
Pull模式:控制台将配置的規則推送至Sentinel用戶端,而用戶端會将配置規則儲存在本地檔案或資料庫中。以後會定時去本地檔案或資料庫中查詢,更新本地規則。
缺點:存在時效性問題,容易導緻資料不一緻
2.Push模式
Push模式:控制台将配置規則推送到遠端配置中心,例如
Nacos
。
Sentinel
用戶端監聽
Nacos
,擷取配置變更的推送消息,完成本地配置更新。
2.實作push模式
1.引入依賴
在微服務中引入
sentinel-datasource-nacos
,監聽
Nacos
的依賴。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2.配置Nacos位址
在微服務的配置資訊中配置
Nacos
位址以及監聽的配置資訊
spring:
cloud:
sentinel:
datasource:
#流控
flow:
nacos:
server-addr: localhost:8848
data-id: orderserver-flow-rules #最終的配置檔案名稱
group-id: SENTINEL_GROUP # 組ID
rule-type: flow # 還可以是其他,具體請檢視 RuleType 的執行個體,需要配置多個時複制多份即可
#降級
degrade:
nacos:
server-addr: localhost:8848
data-id: orderserver-degrade-rules #最終的配置檔案名稱
group-id: SENTINEL_GROUP # 組ID
rule-type: degrade
3.修改sentinel源碼
Sentinel-Dashboard
預設不支援nacos持久化,需要修改源碼
在
Github
上下載下傳
sentinel
的源碼,使用 IDE工具打開
1.在sentinel-dashboard源碼的pom檔案中,nacos的依賴預設的scope是test,隻能在測試時使用,這裡要去除:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2.在sentinel-dashboard的test包下,已經編寫了對nacos的支援,我們需要将其拷貝到main下。
3.修改
nacos
的位址,修改測試代碼中的
NacosConfig
類,讓其讀取application.properties中的值
@Configuration
@ConfigurationProperties(prefix = "nacos")
public class NacosConfig {
/**
* nacos位址
*/
private String addr;
@Bean
public ConfigService nacosConfigService() throws Exception {
return ConfigFactory.createConfigService(addr);
}
public void setAddr(String addr){
this.addr=addr;
}
public String getAddr(){
return this.addr;
}
@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}
@Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
}
4.在
sentinel-dashboard
的
application.properties
中添加nacos位址配置:
nacos.addr=localhost:8848
5.需要修改
com.alibaba.csp.sentinel.dashboard.controller.v2
包下的
FlowControllerV2
類,讓添加的Nacos資料源生效
@Autowired
@Qualifier("flowRuleNacosProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleNacosPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
6.修改前端頁面,添加一個支援nacos的菜單,
修改
src/main/webapp/resources/app/scripts/directives/sidebar/
目錄下的
sidebar.html
檔案,放開以下代碼:
<li ui-sref-active="active" ng-if="entry.appType==0">
<a ui-sref="dashboard.flow({app: entry.app})">
<i class="glyphicon glyphicon-filter"></i> 流控規則 V1</a>
</li>
7.去掉測試單元,重新打包項目,然後運作
java -jar sentinel-dashboard-1.8.6.jar
3.添加規則
在 開放出來的子產品中添加流控規則,将會被同步到 Nacos,進而實作持久化
更多内容,參考Setinel官網