前言
在前文中,介紹了 Sentinel 的流控模式和流控效果,然而限流只是一種預(yù)防措施,雖然可以盡量避免因?yàn)椴l(fā)問(wèn)題而引起的服務(wù)故障,但服務(wù)仍然可能因其他因素而發(fā)生故障。為了將這些故障控制在一定范圍內(nèi),以避免雪崩效應(yīng)的發(fā)生,我們需要依賴線程隔離(艙壁模式)和熔斷降級(jí)機(jī)制。
無(wú)論是線程隔離還是熔斷降級(jí),它們都是為了保護(hù)客戶端(調(diào)用方)免受服務(wù)故障的影響。
在微服務(wù)之間的調(diào)用通常依賴于 Open Feign,因此我們首先需要將Feign與 Sentinel進(jìn)行有效整合。
本文將探討 Feign 如何與 Sentinel 整合,以及 Sentinel 的隔離、熔斷降級(jí)規(guī)則以及授權(quán)規(guī)則等關(guān)鍵概念。
一、Feign 整合 Sentinel
1.1 實(shí)現(xiàn)步驟
在 Spring Cloud 中,微服務(wù)之間的調(diào)用通常依賴于 Feign 來(lái)實(shí)現(xiàn)。要在微服務(wù)架構(gòu)中保護(hù)客戶端,需要將 Feign 和 Sentinel 整合在一起。以下是將 Feign 與 Sentinel 整合的步驟,以一個(gè)名為 cloud-demo
的微服務(wù)案例為例:
1. 修改 order-service
的 application.yml
文件,啟用 Feign 對(duì) Sentinel 的支持:
feign:
sentinel:
enabled: true # 啟用 Feign 對(duì) Sentinel 的支持
通過(guò)這個(gè)配置,我們告訴 Feign 在進(jìn)行遠(yuǎn)程調(diào)用時(shí)要與 Sentinel 一起工作,以確??蛻舳耸艿竭m當(dāng)?shù)谋Wo(hù)。
2. 編寫調(diào)用失敗后的降級(jí)邏輯:
當(dāng)遠(yuǎn)程調(diào)用失敗時(shí),可以實(shí)現(xiàn)降級(jí)邏輯。有兩種方式可供選擇:
- 方式一:FallbackClass,F(xiàn)allbackClass 是 Feign 的一種直接降級(jí)處理機(jī)制。它涉及創(chuàng)建一個(gè)實(shí)現(xiàn)原始Feign接口的類,并在該類的方法中定義降級(jí)邏輯。但是這種方式對(duì)遠(yuǎn)程調(diào)用的異常無(wú)法進(jìn)行處理。
- 方式二:FallbackFactory(降級(jí)工廠),F(xiàn)allbackFactory 提供了更靈活的處理遠(yuǎn)程服務(wù)調(diào)用失敗的方式。它允許我們動(dòng)態(tài)創(chuàng)建 Feign 接口的降級(jí)實(shí)例,并獲取特定的異常信息。
1.2 FallbackFactory 示例
在接下來(lái)的部分,我們將深入研究如何使用 FallbackFactory
來(lái)處理遠(yuǎn)程調(diào)用的異常,并為客戶端提供更好的降級(jí)體驗(yàn)。
步驟一:在 feign-api
模塊中創(chuàng)建一個(gè)UserClientFallbackFactory
類,實(shí)現(xiàn)FallbackFactory
接口,并重寫 create
方法:
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User findById(Long id) {
log.error("查詢用戶失??!", throwable);
return new User();
}
};
}
}
這段代碼將在 order-service
調(diào)用 user-service
失敗后自動(dòng)調(diào)用,在控制臺(tái)會(huì)輸出錯(cuò)誤日志,并返回一個(gè)空的 User
對(duì)象。
步驟二:在 config
中將 UserClientFallbackFactory
類注冊(cè)為一個(gè) Bean:
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
步驟三:在 feign
的 UserClient
接口的@FeignClient
注解中指定 fallbackFactory
為 UserClientFallbackFactory
:
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
通過(guò)這些步驟,就可以使用 FallbackFactory
處理遠(yuǎn)程服務(wù)調(diào)用失敗,捕獲異常信息,并提供更好的降級(jí)體驗(yàn)。
二、Sentinel 實(shí)現(xiàn)隔離
在 Sentinel 中,隔離有兩種主要實(shí)現(xiàn)方法,即信號(hào)量隔離和線程隔離。其中默認(rèn)采用的是信號(hào)量隔離:
2.1 隔離的實(shí)現(xiàn)方法
信號(hào)量隔離:
信號(hào)量隔離是一種資源隔離方式,它通過(guò)設(shè)置每個(gè)資源(或接口)的許可證數(shù)量來(lái)控制并發(fā)訪問(wèn)。當(dāng)并發(fā)請(qǐng)求到達(dá)時(shí),如果資源的許可證數(shù)量已經(jīng)用盡,新的請(qǐng)求將被阻塞,以保護(hù)資源不被過(guò)度訪問(wèn)。信號(hào)量隔離適用于需要控制并發(fā)訪問(wèn)的場(chǎng)景,例如數(shù)據(jù)庫(kù)連接、外部API調(diào)用等。
優(yōu)點(diǎn):
- 輕量級(jí),無(wú)額外開銷
- 適用于高頻調(diào)用和高扇出場(chǎng)景
缺點(diǎn):
- 不支持主動(dòng)超時(shí)
- 不支持異步調(diào)用
場(chǎng)景:適用于高頻調(diào)用和高扇出的場(chǎng)景,其中資源隔離較為輕量。
線程隔離:
線程隔離是一種資源隔離方式,它將不同的資源請(qǐng)求隔離到不同的線程池中執(zhí)行,以確保它們不會(huì)相互影響。每個(gè)線程池負(fù)責(zé)執(zhí)行特定資源的請(qǐng)求,如果一個(gè)請(qǐng)求由于某種原因?qū)е戮€程阻塞或異常,不會(huì)影響其他資源的請(qǐng)求。線程隔離適用于需要獨(dú)立線程執(zhí)行的場(chǎng)景,例如耗時(shí)操作、阻塞調(diào)用等。
優(yōu)點(diǎn):
- 支持主動(dòng)超時(shí)
- 支持異步調(diào)用
缺點(diǎn):
- 線程的額外開銷較大
場(chǎng)景:線程隔離適用于低扇出場(chǎng)景,其中資源隔離更為重要,或需要支持異步調(diào)用和主動(dòng)超時(shí)的情況。
選擇隔離的實(shí)現(xiàn)方法取決于具體需求和應(yīng)用場(chǎng)景。根據(jù)不同的場(chǎng)景和資源調(diào)用特點(diǎn),可以靈活選擇信號(hào)量隔離或線程隔離,以保障系統(tǒng)的穩(wěn)定性和性能。
2.2 Sentinel 實(shí)現(xiàn)線程隔離示例
回顧在使用 Sentinel 控制臺(tái)設(shè)置限流規(guī)則的時(shí)候,發(fā)現(xiàn)有兩種閾值類型:
- QPS: 就是每秒的請(qǐng)求數(shù),在快速入門中已經(jīng)演示過(guò)
- 線程數(shù): 是該資源能使用用的tomcat線程數(shù)的最大值。也就是通過(guò)限制線程數(shù)量,實(shí)現(xiàn)艙壁模式。
下面是一個(gè)示例,演示如何在 Sentinel 的控制臺(tái)中實(shí)現(xiàn)線程隔離的設(shè)置。
1. 給 UserClient
的查詢用戶接口設(shè)置流控規(guī)則,線程數(shù)不能超過(guò) 2。
2. 然后利用 JMeter 測(cè)試。
設(shè)置線程數(shù)為 10:
設(shè)置 HTTP 請(qǐng)求:
啟動(dòng) JMeter:
可以發(fā)現(xiàn)最終 10 個(gè)線程的請(qǐng)求只通過(guò)了其中兩個(gè)。
查看 Sentinel 控制臺(tái)的實(shí)時(shí)監(jiān)控:
三、熔斷降級(jí)規(guī)則
3.1 熔斷降級(jí)原理及其流程
熔斷降級(jí)原理:
熔斷降級(jí)是解決雪崩問(wèn)題的重要手段,其原理是由斷路器統(tǒng)計(jì)服務(wù)調(diào)用的異常比例和慢請(qǐng)求比例,如果超出閾值則會(huì)熔斷該服務(wù)。具體原理如下:
- 關(guān)閉狀態(tài)(Closed):初始狀態(tài)下,斷路器處于關(guān)閉狀態(tài),所有請(qǐng)求都會(huì)被允許訪問(wèn)服務(wù)。
- 熔斷狀態(tài)(Open):當(dāng)服務(wù)調(diào)用的異常比例或慢請(qǐng)求比例超出閾值時(shí),斷路器會(huì)進(jìn)入熔斷狀態(tài),攔截一定比例的請(qǐng)求,這些請(qǐng)求將快速失敗,不會(huì)真正訪問(wèn)服務(wù)。
- 半開狀態(tài)(Half-Open):在一段時(shí)間后,斷路器會(huì)進(jìn)入半開狀態(tài),允許部分請(qǐng)求訪問(wèn)服務(wù),用于測(cè)試服務(wù)是否已經(jīng)恢復(fù)正常。
- 關(guān)閉狀態(tài)(Closed)或繼續(xù)熔斷狀態(tài)(Open):根據(jù)半開狀態(tài)下的請(qǐng)求成功與否,斷路器會(huì)決定是繼續(xù)保持熔斷狀態(tài)還是恢復(fù)到關(guān)閉狀態(tài)。
熔斷降級(jí)流程:
熔斷降級(jí)的流程如下圖所示:
流程說(shuō)明如下:
- 斷路器初始處于
Closed
狀態(tài),允許所有請(qǐng)求訪問(wèn)服務(wù)。 - 當(dāng)服務(wù)調(diào)用失敗次數(shù)或慢請(qǐng)求比例達(dá)到閾值,斷路器進(jìn)入
Open
狀態(tài),攔截所有請(qǐng)求,快速失敗。 - 在一段時(shí)間后,斷路器進(jìn)入
Half-Open
狀態(tài),允許部分請(qǐng)求訪問(wèn)服務(wù),用于測(cè)試服務(wù)是否已經(jīng)恢復(fù)正常。 - 如果在
Half-Open
狀態(tài)下的請(qǐng)求成功,則斷路器進(jìn)入Closed
狀態(tài),允許所有請(qǐng)求訪問(wèn)服務(wù)。 - 如果在
Half-Open
狀態(tài)下的請(qǐng)求仍然失敗,斷路器繼續(xù)保持Open
狀態(tài),直到下一次嘗試進(jìn)入Half-Open
狀態(tài)。
熔斷降級(jí)通過(guò)這種狀態(tài)機(jī)實(shí)現(xiàn),可以幫助服務(wù)在異常情況下避免雪崩效應(yīng),提高系統(tǒng)的可用性和穩(wěn)定性。
3.2 熔斷策略 —— 慢調(diào)用
斷路器熔斷策略有三種:慢調(diào)用、異常比例、異常數(shù)。
慢調(diào)用就是當(dāng)業(yè)務(wù)的響應(yīng)時(shí)長(zhǎng)(RT)大于指定時(shí)長(zhǎng)的請(qǐng)求認(rèn)定為慢調(diào)用請(qǐng)求。在指定時(shí)間內(nèi),如果請(qǐng)求數(shù)量超過(guò)設(shè)定的最小數(shù)量,慢調(diào)用比例大于設(shè)定的閾值,則觸發(fā)熔斷。
例如,通過(guò) Sentinel 控制臺(tái)設(shè)置慢調(diào)用降級(jí)策略:
說(shuō)明:
當(dāng) RT 超過(guò) 500ms 的調(diào)用就是慢調(diào)用,統(tǒng)計(jì)最近 10000ms 內(nèi)的請(qǐng)求,如果請(qǐng)求量超過(guò) 5 次,并且慢調(diào)用比例不低于 0.5,則觸發(fā)熔斷,熔斷時(shí)長(zhǎng)為 5 秒。然后進(jìn)入 Half-open
狀態(tài),放行一次請(qǐng)求做測(cè)試。
現(xiàn)在有一個(gè)需求:就是給 UserClient
的查詢用戶接口設(shè)置降級(jí)規(guī)則,慢調(diào)用的 RT 閾值為 50ms,統(tǒng)計(jì)時(shí)間為 1 秒,最小請(qǐng)求數(shù)量為 5,失敗閾值比例為 0.4,熔斷時(shí)長(zhǎng)為 5:
為了觸發(fā)慢調(diào)用規(guī)則,我們需要修改UserService
中的業(yè)務(wù),增加業(yè)務(wù)耗時(shí):
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id ) throws InterruptedException {
if(id == 1){
// 休眠,觸發(fā)慢調(diào)用熔斷策略
Thread.sleep(60);
}
return userService.queryById(id);
}
重啟 user-service
服務(wù)后,我們可以通過(guò)快速刷新瀏覽器,來(lái)觸發(fā)這個(gè)熔斷機(jī)制:
當(dāng)觸發(fā)了容器之后,服務(wù)其他 ID 的接口,也會(huì)被熔斷:
3.3 熔斷策略 —— 異常比例和異常數(shù)
異常比例或異常數(shù)都是統(tǒng)計(jì)指定時(shí)間內(nèi)的調(diào)用,如果調(diào)用次數(shù)超過(guò)指定請(qǐng)求數(shù),并且出現(xiàn)異常的比例達(dá)到設(shè)定的比例閾值(或超過(guò)指定異常數(shù)),則觸發(fā)熔斷。
同樣可以通過(guò) Sentinel 控制臺(tái)進(jìn)行設(shè)置:
說(shuō)明:
統(tǒng)計(jì)最近 1000ms 內(nèi)的請(qǐng)求,如果請(qǐng)求量超過(guò) 10 次,并且異常比例不低于 0.5,則觸發(fā)熔斷,熔斷時(shí)長(zhǎng)為5秒。然后進(jìn)入Half-open
狀態(tài),放行一次請(qǐng)求做測(cè)試。
下面以異常比例為例:
例如現(xiàn)在有一個(gè)需求:就是給 UserClient
的查詢用戶接口設(shè)置降級(jí)規(guī)則,統(tǒng)計(jì)時(shí)間為 1 秒,最小請(qǐng)求數(shù)量為 5,失敗閾值比例為 0.4,熔斷時(shí)長(zhǎng)為 5s:
為了觸發(fā)異常統(tǒng)計(jì),同樣需要修改UserService
中的業(yè)務(wù),拋出異常:
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) throws InterruptedException {
if(id == 1){
// 休眠,觸發(fā)慢調(diào)用熔斷策略
Thread.sleep(60);
} else if (id == 2) {
throw new RuntimeException("演示觸發(fā)異常比例熔斷!");
}
return userService.queryById(id);
}
此時(shí),訪問(wèn) ID 為 2 的用戶就會(huì)拋出異常。
重啟 user-service
服務(wù)后,我們可以通過(guò)快速刷新瀏覽器,來(lái)觸發(fā)這個(gè)熔斷機(jī)制:
四、授權(quán)規(guī)則
4.1 什么是授權(quán)規(guī)則
授權(quán)規(guī)則用于對(duì)調(diào)用方的來(lái)源進(jìn)行控制,通常分為白名單和黑名單兩種方式:
- 白名單:將指定的來(lái)源(origin)加入白名單,允許這些調(diào)用者訪問(wèn)服務(wù)。
- 黑名單:將指定的來(lái)源(origin)加入黑名單,不允許這些調(diào)用者訪問(wèn)服務(wù)。
在 Sentinel 控制臺(tái)中,可以配置授權(quán)規(guī)則,如下所示:
現(xiàn)在,我們可以從瀏覽器和網(wǎng)關(guān)兩個(gè)路徑去服務(wù) order-service
服務(wù):
如果現(xiàn)在需要限定只允許從網(wǎng)關(guān)來(lái)的請(qǐng)求訪問(wèn) order-service
服務(wù),那么流控應(yīng)用中就填寫網(wǎng)關(guān)的名稱。
4.2 授權(quán)規(guī)則示例
下面將演示如何通過(guò) Sentinel 的授權(quán)規(guī)則來(lái)限制對(duì) order-service
服務(wù)的請(qǐng)求只能來(lái)自網(wǎng)關(guān) gateway
。
Sentinel 是通過(guò) RequestOriginParser
這個(gè)接口的 parseOrigin
來(lái)獲取請(qǐng)求的來(lái)源的:
public interface RequestOriginParser {
// 從請(qǐng)求request對(duì)象中獲取origin,獲取方式自定義
String parseOrigin(HttpServletRequest request);
}
RequestOriginParser
是 Sentinel 提供的接口,用于解析請(qǐng)求的來(lái)源(origin)。這接口定義了一個(gè)方法 parseOrigin
,我們需要實(shí)現(xiàn)這個(gè)方法,以自定義方式從請(qǐng)求對(duì)象中獲取請(qǐng)求的來(lái)源信息。
因此,我們嘗試從request
中獲取一個(gè)名為origin
的請(qǐng)求頭,作為origin
的值,作為判斷請(qǐng)求是否來(lái)源于網(wǎng)關(guān)的依據(jù)。實(shí)現(xiàn)的步驟如下:
- 首先,在
gateway
服務(wù)的application.yml
文件中,利用網(wǎng)關(guān)的過(guò)濾器給所有的請(qǐng)求都添加一個(gè)名為gateway
的origin
請(qǐng)求頭:
spring:
cloud:
gateway:
default-filters: # 默認(rèn)過(guò)濾器,會(huì)對(duì)所有的路由請(qǐng)求都生效
- AddRequestHeader=origin, gateway # Sentinel 授權(quán)規(guī)則,只有從網(wǎng)關(guān)服務(wù)的才合法,通過(guò)添加請(qǐng)求頭標(biāo)識(shí)
- 然后在 Sentinel 控制臺(tái)中添加授權(quán)規(guī)則,指定流控應(yīng)用為
gateway
:
- 在
order-service
中實(shí)現(xiàn)一個(gè)HeadOriginParser
類,實(shí)現(xiàn)RequestOriginParser
接口,用來(lái)獲取請(qǐng)求源:
@Component
public class HeadOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// 1. 獲取請(qǐng)求頭
String origin = httpServletRequest.getHeader("origin");
// 2. 非空判斷
if (StringUtils.isEmpty(origin)) {
return "blank";
}
return origin;
}
}
如果不存在origin
這個(gè)字段的請(qǐng)求頭,直接返回"blank",否則直接返回 origin
的值,然后交給 Sentinel 進(jìn)行判斷請(qǐng)求源。
- 重啟
order-service
和gateway
服務(wù),進(jìn)行演示:
此時(shí),如果我們之間通過(guò)瀏覽器訪問(wèn) order-service
的接口,發(fā)現(xiàn)就被禁止訪問(wèn)了:
如果通過(guò) gateway
網(wǎng)關(guān),就能夠成功訪問(wèn)了:
五、自定義異常返回結(jié)果
默認(rèn)情況下,發(fā)生限流、降級(jí)、授權(quán)攔截時(shí),都會(huì)拋出異常到調(diào)用方,但是通過(guò)上面所有的例子,我們發(fā)現(xiàn)都只返回了一種結(jié)果,那就是“Blocked by Sentinel (flow limiting)”。如果我們想要知道微服務(wù)調(diào)用失敗的具體原因,就需要對(duì)異常進(jìn)行自定義處理:
如果要實(shí)現(xiàn)對(duì) Sentinel 的異常實(shí)現(xiàn)自定義,那么就需要實(shí)現(xiàn) BlockExceptionHandler
接口,它是 Sentinel 提供的一個(gè)接口,用于自定義處理調(diào)用失敗時(shí)的異常情況。
而關(guān)于 BlockException
這個(gè)類,包含很多個(gè)子類,分別對(duì)應(yīng)不同的場(chǎng)景:
異常 | 說(shuō)明 |
---|---|
FlowException | 限流異常 |
ParamFlowException | 熱點(diǎn)參數(shù)限流的異常 |
DegradeException | 降級(jí)異常 |
AuthorityException | 授權(quán)規(guī)則異常 |
SystemBlockException | 系統(tǒng)規(guī)則異常 |
我們可以通過(guò)這些子類來(lái)判斷在調(diào)用微服務(wù)失敗的時(shí)候,具體出現(xiàn)了哪種情況,然后通過(guò)自定義異常進(jìn)行結(jié)果的返回,下面是一個(gè)實(shí)現(xiàn)自定義異常的代碼示例:
@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 = "請(qǐng)求被限流了";
} else if (e instanceof ParamFlowException) {
msg = "請(qǐng)求被熱點(diǎn)參數(shù)限流";
} else if (e instanceof DegradeException) {
msg = "請(qǐng)求被降級(jí)了";
} else if (e instanceof AuthorityException) {
msg = "沒(méi)有權(quán)限訪問(wèn)";
status = 401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
}
}
在上述示例中,創(chuàng)建了一個(gè)名為 CustomBlockExceptionHandler
的自定義異常處理類,實(shí)現(xiàn)了 BlockExceptionHandler
接口。在重寫的 handle
方法中,根據(jù)不同的 BlockException
類型來(lái)確定異常消息和狀態(tài)碼,然后將這些信息返回給調(diào)用方。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-719859.html
例如,在上面配置了授權(quán)規(guī)則的情況下,直接通過(guò)瀏覽器訪問(wèn) order-service
服務(wù):
此時(shí),就能夠清楚的知道微服務(wù)調(diào)用失敗的原因了。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-719859.html
到了這里,關(guān)于【微服務(wù)】Feign 整合 Sentinel,深入探索 Sentinel 的隔離和熔斷降級(jí)規(guī)則,以及授權(quán)規(guī)則和自定義異常返回結(jié)果的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!