雪崩問題
微服務調用鏈路中的某個服務故障,引起整個鏈路中的所有微服務都不可用,這就是雪崩
總結:當一個服務發(fā)生故障導致所有與它相關的鏈路都是不可以用的

解決雪崩方式
解決雪崩問題的常見方式有四種:
-
超時處理:設定超時時間,請求超過一定時間沒有響應就返回錯誤信息,不會無休止等待
image.png -
艙壁模式:限定每個業(yè)務能使用的線程數,避免耗盡整個tomcat的資源,因此也叫線程隔離。
image.png -
熔斷降級:由斷路器統(tǒng)計業(yè)務執(zhí)行的異常比例,如果超出閾值則會熔斷該業(yè)務,攔截訪問該業(yè)務的一切請求。
image.png -
流量控制:限制業(yè)務訪問的QPS,避免服務因流量的突增而故障。
image.png
Sentinel與Hystrix比較
我前面梳理了Hystrix的知識,現在梳理的Sentinel與它的性質是一樣的,都是對于服務的保護,而現在Hystrix不再更新維護,導致替代者Sentinel順應市場需求

認識Sentinel
Sentinel是阿里巴巴開源的一款微服務流量控制組件。官網地址:https://sentinelguard.io/zh-cn/index.html
Sentinel 具有以下特征:
- 豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發(fā)流量控制在系統(tǒng)容量可以承受的范圍)、消息削峰填谷、集群流量控制、實時熔斷下游不可用應用等。
- 完備的實時監(jiān)控:Sentinel 同時提供實時的監(jiān)控功能。您可以在控制臺中看到接入應用的單臺機器秒級數據,甚至 500 臺以下規(guī)模的集群的匯總運行情況。
- 廣泛的開源生態(tài):Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴并進行簡單的配置即可快速地接入 Sentinel。
- 完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您可以通過實現擴展接口來快速地定制邏輯。例如定制規(guī)則管理、適配動態(tài)數據源等。
安裝Sentinel
sentinel官方提供了UI控制臺,方便我們對系統(tǒng)做限流設置。大家可以在GitHub下載。

運行方式:
java -jar sentinel-dashboard-1.8.4.jar
賬號密碼以及登入方式是:
然后訪問:localhost:8080 即可看到控制臺頁面,默認的賬戶和密碼都是sentinel
如果端口被占用:
java -jar sentinel-dashboard-1.8.4.jar -Dserver.port = 你輸入的端口號
其他常用參數如下:

整和微服務模塊
<!--sentinel 上面有父模塊所以不加版本號,沒有就需要加入版本號-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 #這個地址是你啟動Sentinel的地址,本地啟動就是localhost或者是127.0.0.1 其他地方就是那個ip地址
查詢IP地址
win:
ipconfig
linux:
ifconfig
總結:整和之后訪問任意接口都會觸發(fā)sentinel監(jiān)控
限流規(guī)則
簇點鏈路:
- 就是項目內的調用鏈路,鏈路中被監(jiān)控的每個接口就是一個資源。默認情況下sentinel會監(jiān)控SpringMVC的每一個端點(Endpoint),因此SpringMVC的每一個端點(Endpoint)就是調用鏈路中的一個資源。
-
流控、熔斷等都是針對簇點鏈路中的資源來設置的,因此我們可以點擊對應資源后面的按鈕來設置規(guī)則:
image.png
點擊資源/order/{orderId}后面的流控按鈕,就可以彈出表單。表單中可以添加流控規(guī)則,如下圖所示:
image.png
流控模式
在添加限流規(guī)則時,點擊高級選項,可以選擇三種流控模式:
- 直接:統(tǒng)計當前資源的請求,觸發(fā)閾值時對當前資源直接限流,也是默認的模式
- 關聯:統(tǒng)計與當前資源相關的另一個資源,觸發(fā)閾值時,對當前資源限流
- 鏈路:統(tǒng)計從指定鏈路訪問到本資源的請求,觸發(fā)閾值時,對指定鏈路限流
image.png
流控模式-關聯
- 關聯模式:統(tǒng)計與當前資源相關的另一個資源,觸發(fā)閾值時,對當前資源限流
-
使用場景:比如用戶支付時需要修改訂單狀態(tài),同時用戶要查詢訂單。查詢和修改操作會爭搶數據庫鎖,產生競爭。業(yè)務需求是有限支付和更新訂單的業(yè)務,因此當修改訂單業(yè)務觸發(fā)閾值時,需要對查詢訂單業(yè)務限流。
image.png
當/write資源訪問量觸發(fā)閾值時,就會對/read資源限流,避免影響/write資源。
流控模式-鏈路
鏈路模式:只針對從指定鏈路訪問到本資源的請求做統(tǒng)計,判斷是否超過閾值。
例如有兩條請求鏈路:
- /test1 -> /common
-
/test2 -> /common
如果只希望統(tǒng)計從/test2進入到/common的請求,則可以這樣配置:
image.png - Sentinel默認只標記Controller中的方法為資源,如果要標記其它方法,需要利用@SentinelResource注解,示例:
@SentinelResource("goods")
public void queryGoods() {
System.err.println("查詢商品");
}
- Sentinel默認會將Controller方法做context整合,導致鏈路模式的流控失效,需要修改application.yml,添加配
spring:
cloud:
sentinel:
web-context-unify: false # 關閉context整合
流控效果
流控效果是指請求達到流控閾值時應該采取的措施,包括三種:
- 快速失?。哼_到閾值后,新的請求會被立即拒絕并拋出FlowException異常。是默認的處理方式。
- warm up:預熱模式,對超出閾值的請求同樣是拒絕并拋出異常。但這種模式閾值會動態(tài)變化,從一個較小值逐漸增加到最大閾值。
- 排隊等待:讓所有的請求按照先后次序排隊執(zhí)行,兩個請求的間隔不能小于指定時長
image.png
流控效果-warm up
warm up也叫預熱模式,是應對服務冷啟動的一種方案。請求閾值初始值是 threshold / coldFactor,持續(xù)指定時長后,逐漸提高到threshold值。而coldFactor的默認值是3.
例如,我設置QPS的threshold為10,預熱時間為5秒,那么初始閾值就是 10 / 3 ,也就是3,然后在5秒后逐漸增長到10.
image.png
流控效果-排隊等待
當請求超過QPS閾值時,快速失敗和warm up 會拒絕新的請求并拋出異常。而排隊等待則是讓所有請求進入一個隊列中,然后按照閾值允許的時間間隔依次執(zhí)行。后來的請求必須等待前面執(zhí)行完成,如果請求預期的等待時間超出最大時長,則會被拒絕。
例如:QPS = 5,意味著每200ms處理一個隊列中的請求;timeout = 2000,意味著預期等待超過2000ms的請求會被拒絕并拋出異常
image.png
熱點參數限流
之前的限流是統(tǒng)計訪問某個資源的所有請求,判斷是否超過QPS閾值。而熱點參數限流是分別統(tǒng)計參數值相同的請求,判斷是否超過QPS閾值。

在熱點參數限流的高級選項中,可以對部分參數設置例外配置:

結合上一個配置,這里的含義是對0號的long類型參數限流,每1秒相同參數的QPS不能超過5,有兩個例外:
- 如果參數值是2,則每1秒允許的QPS為10
- 如果參數值是3,則每1秒允許的QPS為10
注意:直接點擊熱點添加沒有高級選擇,點擊熱點規(guī)則里面添加熱點規(guī)則才有高級選擇
image.png
image.png
image.png
隔離和降級
雖然限流可以盡量避免因高并發(fā)而引起的服務故障,但服務還會因為其它原因而故障。而要將這些故障控制在一定范圍,避免雪崩,就要靠線程隔離(艙壁模式)和熔斷降級手段了。
不管是線程隔離還是熔斷降級,都是對客戶端(調用方)的保護。

Feign整合Sentinel
SpringCloud中,微服務調用都是通過Feign來實現的,因此做客戶端保護必須整合Feign和Sentinel。
- 修改OrderService的application.yml文件,開啟Feign的Sentinel功能
feign:
sentinel:
enabled: true # 開啟Feign的Sentinel功能
- 給FeignClient編寫失敗后的降級邏輯
- 方式一:FallbackClass,無法對遠程調用的異常做處理
- 方式二:FallbackFactory,可以對遠程調用的異常做處理,我們選擇這種
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
// 創(chuàng)建UserClient接口實現類,實現其中的方法,編寫失敗降級的處理邏輯
return new UserClient() {
@Override
public User findById(Long id) {
// 記錄異常信息
log.error("查詢用戶失敗", throwable);
// 根據業(yè)務需求返回默認的數據,這里是空用戶
return new User();
}
};
}
}
線程隔離
線程隔離有兩種方式實現:
- 線程池隔離
- 信號量隔離(Sentinel默認采用)
image.png
image.png
線程隔離(艙壁模式)
在添加限流規(guī)則時,可以選擇兩種閾值類型:
image.png - QPS:就是每秒的請求數,在上面說了
- 線程數:是該資源能使用用的tomcat線程數的最大值。也就是通過限制線程數量,實現艙壁模式。
熔斷降級
熔斷降級是解決雪崩問題的重要手段。其思路是由斷路器統(tǒng)計服務調用的異常比例、慢請求比例,如果超出閾值則會熔斷該服務。即攔截訪問該服務的一切請求;而當服務恢復時,斷路器會放行訪問該服務的請求。

熔斷策略-慢調用
斷路器熔斷策略有三種:慢調用、異常比例、異常數
- 慢調用:業(yè)務的響應時長(RT)大于指定時長的請求認定為慢調用請求。在指定時間內,如果請求數量超過設定的最小數量,慢調用比例大于設定的閾值,則觸發(fā)熔斷。例如:
image.png
解讀:RT超過500ms的調用是慢調用,統(tǒng)計最近10000ms內的請求,如果請求量超過10次,并且慢調用比例不低于0.5,則觸發(fā)熔斷,熔斷時長為5秒。然后進入half-open狀態(tài),放行一次請求做測試。
熔斷策略-異常比例、異常數
斷路器熔斷策略有三種:慢調用、異常比例或異常數
- 異常比例或異常數:統(tǒng)計指定時間內的調用,如果調用次數超過指定請求數,并且出現異常的比例達到設定的比例閾值(或超過指定異常數),則觸發(fā)熔斷。例如:
image.png
解讀:統(tǒng)計最近1000ms內的請求,如果請求量超過10次,并且異常比例不低于0.5,則觸發(fā)熔斷,熔斷時長為5秒。然后進入half-open狀態(tài),放行一次請求做測試。
授權規(guī)則
授權規(guī)則可以對調用方的來源做控制,有白名單和黑名單兩種方式。
- 白名單:來源(origin)在白名單內的調用者允許訪問
-
黑名單:來源(origin)在黑名單內的調用者不允許訪問
image.png
例如,我們限定只允許從網關來的請求訪問order-service,那么流控應用中就填寫網關的名稱

注意:在sentinel內部有個RequestOriginParser內部不能區(qū)別來源,只會返回Defaule需要自己配合過濾器來增加header里面的請求參數來判斷來源,總體來說就是根據你指定的來源才能訪問的性質才能訪問你的接口,來起到保護的效果
代碼展現:
@Component
public class SentinelOriginRequest implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String origin = httpServletRequest.getHeader("origin");
if (StringUtils.isEmpty(origin)) {
origin = "blank";
}
return origin;
}
}
解讀:這里我實現了sentinel的RequestOriginParser這個類,并且把這個對象變?yōu)榱私M件,在header里面加入了origin這個參數,如果攜帶了這個參數就返回這個參數代表允許
自定義異常結果
默認情況下,發(fā)生限流、降級、授權攔截時,都會拋出異常到調用方。如果要自定義異常時的返回結果,需要實現BlockExceptionHandler接口:
@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 + "}");
}
}
而BlockException包含很多個子類,分別對應不同的場景:





















