SpringCloudAlibaba Sentinel降級(jí)和熔斷
接著上篇文章的內(nèi)容,在Sentinel中如何進(jìn)行降級(jí)和熔斷呢?
熔斷降級(jí)規(guī)則
降級(jí)規(guī)則
在Sentinel中降級(jí)主要有三個(gè)策略:RT、異常比例、異常數(shù),也是針對(duì)某個(gè)資源的設(shè)置。而在1.8.0+
版本后RT改為了慢調(diào)用比例
需要設(shè)置允許的慢調(diào)用 RT(即最大的響應(yīng)時(shí)間),請(qǐng)求的響應(yīng)時(shí)間大于該值則統(tǒng)計(jì)為慢調(diào)用。當(dāng)單位統(tǒng)計(jì)時(shí)長(statIntervalMs)內(nèi)請(qǐng)求數(shù)目大于設(shè)置的最小請(qǐng)求數(shù)目,并且慢調(diào)用的比例大于閾值,則接下來的熔斷時(shí)長內(nèi)請(qǐng)求會(huì)自動(dòng)被熔斷。
經(jīng)過熔斷時(shí)長后熔斷器會(huì)進(jìn)入探測恢復(fù)狀態(tài)(HALF-OPEN 狀態(tài)),若接下來的一個(gè)請(qǐng)求響應(yīng)時(shí)間小于設(shè)置的慢調(diào)用 RT 則結(jié)束熔斷,若大于設(shè)置的慢調(diào)用 RT 則會(huì)再次被熔斷。
RT:表示該資源1s內(nèi)處理請(qǐng)求的平均響應(yīng)時(shí)間。
注意:RT值的上限時(shí)4900ms,及時(shí)超過也是4900ms,如需自定義,可以在啟動(dòng)sentinel時(shí)增加參數(shù)
-Dcsp.sentinel.statistic.max.rt=x
慢調(diào)用比例
依舊是在簇點(diǎn)鏈路的列表視圖選擇/sentinelTest
一行,進(jìn)入熔斷,設(shè)置參數(shù)如圖:
RT設(shè)置為800ms,熔斷時(shí)長設(shè)置為20s,為了測試效果,把接口睡眠1s。
@RequestMapping("/sentinelTest")
public String sentinelTest() throws InterruptedException {
Thread.sleep(1000);
return "sentinel-consumer9001 sentinelTest" + RandomUtils.nextInt(0, 1000);
}
解讀:響應(yīng)時(shí)間超過RT值的請(qǐng)求被稱為慢調(diào)用。在單位時(shí)間(上圖的統(tǒng)計(jì)時(shí)長1s)內(nèi),請(qǐng)求的數(shù)量大于最小請(qǐng)求數(shù)(5)
,且慢調(diào)用的比例>=閾值,此資源進(jìn)入熔斷狀態(tài)(20s內(nèi)不可用)。
Jmeter請(qǐng)求/sentinelTest
,使用10個(gè)線程執(zhí)行100次結(jié)果。
前面幾個(gè)請(qǐng)求是正常返回?cái)?shù)據(jù),后面全部降級(jí)處理,直接返回提示信息(此時(shí)該資源已經(jīng)進(jìn)入了熔斷狀態(tài),可以理解為家里的電閘給關(guān)了,必須重新打開電閘,才能恢復(fù)使用電力
)。后面這個(gè)資源無論怎樣被調(diào)用,都無法進(jìn)入接口,直接返回提示。
異常比例
當(dāng)單位統(tǒng)計(jì)時(shí)長(statIntervalMs)內(nèi)請(qǐng)求數(shù)目大于設(shè)置的最小請(qǐng)求數(shù)目,并且異常的比例大于閾值,則接下來的熔斷時(shí)長內(nèi)請(qǐng)求會(huì)自動(dòng)被熔斷。
經(jīng)過熔斷時(shí)長后熔斷器會(huì)進(jìn)入探測恢復(fù)狀態(tài)(HALF-OPEN 狀態(tài)),若接下來的一個(gè)請(qǐng)求成功完成(沒有錯(cuò)誤)則結(jié)束熔斷,否則會(huì)再次被熔斷。異常比率的閾值范圍是 [0.0, 1.0],代表 0% - 100%
表示請(qǐng)求該資源的異常總數(shù)占比。先模擬一個(gè)異常
@RequestMapping("/sentinelTest")
public String sentinelTest() {
int i = 1 / 0; // 除數(shù)為0
return "sentinel-consumer9001 sentinelTest" + RandomUtils.nextInt(0, 1000);
}
設(shè)置規(guī)則
解讀:當(dāng)1s內(nèi),請(qǐng)求數(shù)量>5,且異常的比例大于80%,熔斷20s
調(diào)用資源
前幾個(gè)請(qǐng)求正常請(qǐng)求返回異常提示,而后面的所有請(qǐng)求直接被拒絕訪問。
異常數(shù)
該資源近1分鐘內(nèi)的異常數(shù)量。
解讀:當(dāng)1s內(nèi),請(qǐng)求數(shù)量>5,且異常的數(shù)量>=10,熔斷20s
經(jīng)過熔斷時(shí)長后熔斷器會(huì)進(jìn)入探測恢復(fù)狀態(tài)(HALF-OPEN 狀態(tài)),若接下來的一個(gè)請(qǐng)求成功完成(沒有錯(cuò)誤)則結(jié)束熔斷,否則會(huì)再次被熔斷。
當(dāng)?shù)?1個(gè)請(qǐng)求仍然是異常時(shí),直接熔斷。
系統(tǒng)規(guī)則
之前的所有規(guī)則都是針對(duì)某個(gè)資源(接口)而言的,后面我們將針對(duì)整個(gè)應(yīng)用設(shè)置系統(tǒng)規(guī)則。相對(duì)更加粗粒度,屬于應(yīng)用級(jí)別的入口流量控制。那么相對(duì)應(yīng)的也有幾種規(guī)則:
- LOAD:負(fù)載,當(dāng)系統(tǒng)負(fù)載超過設(shè)定值,且發(fā)現(xiàn)線程數(shù)超過預(yù)估系統(tǒng)容量就會(huì)觸發(fā)保護(hù)機(jī)制。
- RT:整個(gè)應(yīng)用上所有資源平均的響應(yīng)時(shí)間,而不是固定某個(gè)資源
- 線程數(shù):設(shè)定整個(gè)系統(tǒng)所能使用的業(yè)務(wù)線程數(shù)閾值,不固定某個(gè)資源
- 入口QPS:整個(gè)應(yīng)用所有的每秒處理的請(qǐng)求數(shù)
- CPU使用率:這個(gè)應(yīng)用占用的CPU的百分比
使用時(shí)可以根據(jù)服務(wù)器的情況設(shè)置即可。
授權(quán)規(guī)則
授權(quán)規(guī)則是根據(jù)調(diào)用方判斷調(diào)用資源的請(qǐng)求是否應(yīng)該被允許訪問。Sentinel提供了黑白名單的授權(quán)類型,白名單表示允許調(diào)用資源,黑名單則不允許調(diào)用資源。
在java中實(shí)現(xiàn)相關(guān)的接口,將返回值交給sentinel處理。(注意:這里是在服務(wù)提供者方設(shè)置的
)
@Component
public class CustomRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String origin = httpServletRequest.getParameter("origin"); // 區(qū)分來源,本質(zhì)通過request域獲取來源標(biāo)識(shí)
if (StringUtils.isEmpty(origin)) {
throw new RuntimeException("origin不能為空");
}
return origin; // 將返回的結(jié)果交給sentinel處理
}
}
然后配置個(gè)授權(quán)規(guī)則
資源名/test
設(shè)置app為黑名單
當(dāng)請(qǐng)求provider服務(wù)上的接口時(shí),若origin為空則會(huì)被拒絕訪問,若origin=app時(shí)仍會(huì)被拒絕,而其他的值則是可以訪問
D:\springcloud\doc>curl localhost:8002/test?origin=app
==>Blocked by Sentinel (flow limiting)
D:\springcloud\doc>curl localhost:8002/test?origin=pc
==>sentinel-provider8002 test()921
使用@SentinelResource注解
之前主要是利用Sentinel儀表板控制一些參數(shù)保護(hù)應(yīng)用。后面我們使用@SentinelResource注解根據(jù)實(shí)際情況實(shí)現(xiàn)定制化功能,對(duì)應(yīng)用的保護(hù)更加細(xì)粒度。
現(xiàn)在限制達(dá)到閾值時(shí),直接提示Blocked by Sentinel(flow limiting)
,提示不太友好,需要實(shí)現(xiàn)更精細(xì)化的控制。
blockHandler屬性–負(fù)責(zé)響應(yīng)控制面板配置
添加一個(gè)接口/blockHandlerTest
資源名為blockHandlerTest,如果違反Sentinel控制臺(tái)的規(guī)則,則進(jìn)入blockHandlerTestHander。
@RequestMapping("/blockHandlerTest")
@SentinelResource(value = "blockHandlerTest", blockHandler = "blockHandlerTestHandler")
public String blockHandlerTest(String params) {
return "Test#blockHandlerTest" + RandomUtils.nextInt(0, 1000);
}
public String blockHandlerTestHandler(String params, BlockException bl) {
return "Test#blockHandlerTest" + RandomUtils.nextInt(0, 1000) + bl.getMessage();
}
注意:blockHandlerTestHandler方法的返回值要和原方法一致,并且除了原有的參數(shù),還要帶上BlockException的參數(shù)
設(shè)置一個(gè)流控,在@SentinelResouce注解中我們把資源名設(shè)置為blockHandlerTest
,那么設(shè)置流控也是針對(duì)這個(gè)資源
設(shè)置,讓后面的請(qǐng)求進(jìn)入我們自定義的處理中。
可以看到,流控超過閾值后,其他的所有請(qǐng)求都是走的我們自定義的處理器。
熱點(diǎn)規(guī)則
在一段時(shí)間內(nèi)訪問很頻繁的資源是熱點(diǎn)資源,需要針對(duì)資源做參數(shù)化定制。
@RequestMapping("/testHotKeyA")
@SentinelResource(value = "testHotKeyA", blockHandler = "blockTestHotKeyA")
public String testHotKeyA(@RequestParam(value = "orderId", required = false) String orderId,
@RequestParam(value = "userId", required = false) String userId) {
return "Test#testHotKeyA" + RandomUtils.nextInt(0, 1000);
}
public String blockTestHotKeyA(String orderId, String userId, BlockException bl) {
return "Test#blockTestHotKeyA" + RandomUtils.nextInt(0, 1000) + bl.getMessage();
}
去sentinel頁面上加一個(gè)熱點(diǎn)key規(guī)則
索引從0開始,那么獲取的就是我們的orderId
參數(shù),在調(diào)用/testHotKeyA
時(shí)要加上oderId
參數(shù)否則不生效。
正確進(jìn)入處理。
同時(shí),我們可以對(duì)熱點(diǎn)資源具體的某個(gè)參數(shù)值
做閾值限制。
上圖是對(duì)orderId
為111或222時(shí),閾值設(shè)置為500.再次測試,基本上不會(huì)進(jìn)入自定義的處理中。但是為其他值時(shí)還是會(huì)進(jìn)入我們的自定義處理。
fallback處理
前面是針對(duì)違反sentinel控制臺(tái)規(guī)則做的處理,那么當(dāng)我們的業(yè)務(wù)層面出現(xiàn)問題時(shí),要做異?;貪L等,則要使用fallback處理。同樣是@SentinelResource中的屬性。sentinel-1.6.0之前的版本是不支持針對(duì)業(yè)務(wù)異常處理的
@RequestMapping("/fallbackTest")
@SentinelResource(value = "fallbackTest", fallback = "fallbackHandler")
public String fallbackTest(String params) {
int i = 1 / 0;
return "Test#fallbackTest" + RandomUtils.nextInt(0, 1000);
}
public String fallbackHandler(String params) {
return "Test#fallbackHandler" + RandomUtils.nextInt(0, 1000);
}
所有的請(qǐng)求都進(jìn)入到異常處理的方法中了。
fallback+blockHandler
@RequestMapping("/sentinelUnionTest")
@SentinelResource(value = "sentinelUnionTest", fallback = "sentinelUnionTestFallback", blockHandler = "sentinelUnionTestBlockHandler")
public String sentinelUnionTest(String params) {
int i = 1 / 0;
return "Test#fallbackTest" + RandomUtils.nextInt(0, 1000);
}
public String sentinelUnionTestFallback(String params) {
return "Test#sentinelUnionTestFallback" + RandomUtils.nextInt(0, 1000);
}
public String sentinelUnionTestBlockHandler(String params, BlockException bl) {
return "Test#sentinelUnionTestBlockHandler" + RandomUtils.nextInt(0, 1000) + bl.getMessage();
}
對(duì)sentinelUnionTest
資源設(shè)置流控,調(diào)用接口觀察結(jié)果
第一個(gè)接口是正常進(jìn)入到了fallback處理,然后后面的請(qǐng)求因?yàn)槌^閾值,直接進(jìn)入block處理中了。
忽略異常–exceptionsToIngnore
fallback定義的方法可以針對(duì)所有類型的異常,我們也可以忽略某些異常。
@RequestMapping("/fallbackTest")
@SentinelResource(value = "fallbackTest", fallback = "fallbackHandler", exceptionsToIgnore = ArithmeticException.class)
public String fallbackTest(String params) {
int i = 1 / 0;
return "Test#fallbackTest" + RandomUtils.nextInt(0, 1000);
}
public String fallbackHandler(String params) {
return "Test#fallbackHandler" + RandomUtils.nextInt(0, 1000);
}
模擬了一個(gè)計(jì)算異常,但是此異常被忽略了,所以不會(huì)進(jìn)入到fallbackHandler中進(jìn)行處理,而是直接jvm拋異常給客戶端響應(yīng)。
代碼優(yōu)化
前面的代碼中都是把fallback和block全都寫在了一起,這樣是不符合程序單一性原則的,畢竟controller層有很多之外的邏輯,二來別的類也不好復(fù)用。
sentinel考慮到這些情況,在@SentinelResource中有blockHandlerClass
和fallbackClass
。顧名思義,blockHandlerClass中寫blockHandler函數(shù),fallbackClass中寫fallback的函數(shù)。
// 異常fallback
public class ExceptionHandler {
public static String sentinelTestFallback(String params) {
return "testCon#sentinelTestFallback" + RandomUtils.nextInt(0, 1000);
}
}
// blockHandler處理
public class BlockHandler {
public static String sentinelBlock(String params, BlockException e) {
return "testCon#sentinelBlock" + RandomUtils.nextInt(0, 1000);
}
}
兩個(gè)類中的方法必須是static 修飾的,且參數(shù)要和原方法保持一致,否則無法解析噢文章來源:http://www.zghlxwxcb.cn/news/detail-603394.html
接口原方法文章來源地址http://www.zghlxwxcb.cn/news/detail-603394.html
@RequestMapping("/sentinelUnionTest")
@SentinelResource(value = "sentinelUnionTest",
fallbackClass = ExceptionHandler.class, fallback = "sentinelTestFallback", // 指定類和方法名
blockHandlerClass = BlockHandler.class, blockHandler = "sentinelBlock") // 指定類和方法名
public String sentinelUnionTest(String params) {
int i = 1 / 0;
return "Test#fallbackTest" + RandomUtils.nextInt(0, 1000);
}
到了這里,關(guān)于SpringCloudAlibaba微服務(wù)實(shí)戰(zhàn)系列(四)Sentinel熔斷降級(jí)、異常fallback、block細(xì)致處理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!