在之前的《使用Sentinel實現接口限流》一文中,我們僅依靠引入Spring Cloud Alibaba對Sentinel的整合封裝spring-cloud-starter-alibaba-sentinel
,就完成了對所有Spring MVC接口的限流控制。然而,在實際應用過程中,我們可能需要限流的層面不僅限於接口。可能對於某個方法的調用限流,對於某個外部資源的調用限流等都希望做到控制。呢么,這個時候我們就不得不手工定義需要限流的資源點,並配置相關的限流策略等內容了。
今天這篇我們就來一起學習一下,如何使用@SentinelResource
注解靈活的定義控制資源以及如何配置控制策略。
自定義資源點
下面的例子基於您已經引入了Spring Cloud Alibaba Sentinel為基礎,如果您還不會這些,建議優先閱讀《使用Sentinel實現接口限流》。
第一步:在應用主類中增加注解支持的配置:
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
// 注解支持的配置Bean
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
第二步:在需要通過Sentinel來控制流量的地方使用@SentinelResource
注解,比如下面以控制Service邏輯層的某個方法為例:
@Slf4j
@Service
public class TestService {
@SentinelResource(value = "doSomeThing")
public void doSomeThing(String str) {
log.info(str);
}
}
到這里一個需要被保護的方法就定義完成了。下面我們分別說說,定義了資源點之后,我們如何實現不同的保護策略,包括:限流、降級等。
如何實現限流與熔斷降級
在定義了資源點之后,我們就可以通過Dashboard來設置限流和降級策略來對資源點進行保護了。同時,也可以通過@SentinelResource
來指定出現限流和降級時候的異常處理策略。下面,就來一起分別看看限流和降級都是如何實現的。
實現限流控制
第一步:在Web層調用這個被保護的方法:
@RestController
public class TestController {
@Autowired
private TestService testService;
@GetMapping("/hello")
public String hello() {
estService.doSomeThing("hello " + new Date());
return "didispace.com";
}
}
第二步:啟動測試應用,啟動Sentinel-Dashboard。發一個請求到/hello
接口上,使得Sentinel-Dashboard上可以看到如下圖所示的幾個控制點:
可以看到,除了如之前入門實例中那樣有/hello
資源點之外,多了一個doSomeThing
資源點。可以通過界面為這個資源點設置限流規則,比如將其QPS設置為2。由於/hello
資源不設置限流規則,所以只要請求/hello
接口,就可以直接模擬調用doSomeThing
資源,來觀察限流規則是否生效。
下面可以通過任何你喜歡的工具來調用/hello
接口,只要QPS超過2,那么就會出現如下的錯誤返回,代表限流策略生效了。
此時,服務端的控制台也會有對應的限流報錯日志:
2019-06-27 11:30:43.514 INFO 36898 --- [nio-8001-exec-3] c.d.a.sentinel.service.TestService : aaa
2019-06-27 11:30:43.905 ERROR 36898 --- [nio-8001-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause
com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
實現限流的異常處理
默認情況下,Sentinel對控制資源的限流處理是直接拋出異常,也就是上一節中貼出的日志內容。在沒有合理的業務承接或者前端對接情況下可以這樣,但是正常情況為了更好的用戶業務,都會實現一些被限流之后的特殊處理,我們不希望展示一個生硬的報錯。那么只需要基於上面的例子做一些加工,比如:
@Slf4j
@Service
public class TestService {
@SentinelResource(value = "doSomeThing", blockHandler = "exceptionHandler")
public void doSomeThing(String str) {
log.info(str);
}
// 限流與阻塞處理
public void exceptionHandler(String str, BlockException ex) {
log.error( "blockHandler:" + str, ex);
}
}
主要做了兩件事:
- 通過
@SentinelResource
注解的blockHandler
屬性制定具體的處理函數 - 實現處理函數,該函數的傳參必須與資源點的傳參一樣,並且最后加上
BlockException
異常參數;同時,返回類型也必須一樣。
如果熟悉Hystrix的讀者應該會發現,這樣的設計與HystrixCommand中定義fallback很相似,還是很容易理解的。
完成上面的改動之后,再嘗試訪問接口(注意限流規則需要配置好),此時前端就不會返回異常信息了,后端會打印exceptionHandler
中定義的日志輸出。而在實際應用的時候,只要根據業務需要對限流請求做緩存或者前端提示等都可以基於此方法來實現。
實現熔斷降級
@SentinelResource
注解除了可以用來做限流控制之外,還能實現與Hystrix類似的熔斷降級策略。下面就來具體看看如何使用吧。
第一步:與限流控制一樣,使用@SentinelResource
注解標記資源點,比如:
@Slf4j
@Service
public class TestService {
@SentinelResource(value = "doSomeThing2")
public void doSomeThing2(String str) {
log.info(str);
throw new RuntimeException("發生異常");
}
}
這里在TestService
類中創建了一個新的方法,並使用@SentinelResource
將該資源命名為doSomeThing2
。該方法會拋出異常,以配合后續制定基於異常比例的降級策略(類似Hystrix)。Sentinel相比Hystrix更豐富,還有基於響應時間和異常數的降級策略。
第二步:在Web層調用這個被保護的方法:
@RestController
public class TestController {
@Autowired
private TestService testService;
@GetMapping("/hello2")
public String hello2() {
testService.doSomeThing2("hello2 " + new Date());
return "didispace.com";
}
}
第三步:啟動測試應用,啟動Sentinel-Dashboard。發一個請求到/hello2
接口上,使得Sentinel-Dashboard上可以看到名為doSomeThing2
的資源點。然后點擊”降級“按鈕,為該資源設置降級規則。這里使用異常比例策略,比例設置為0.5(即:50%的異常率),時間窗口設置為2(秒)。
第四步:驗證熔斷降級,根據上面的降級策略配置,當doSomeThing2
方法的調用QPS >= 5,如果異常率超過50%,那么后續2秒內的調用將直接出發熔斷降級,默認情況會直接拋出DegradeException
異常,比如:
2019-06-27 17:49:58.913 ERROR 99863 --- [nio-8001-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause
com.alibaba.csp.sentinel.slots.block.degrade.DegradeException: null
熔斷的降級處理
在Sentinel中定義熔斷的降級處理方法非常簡單,與Hystrix非常相似。只需要使用@SentinelResource
注解的fallback
屬性來指定具體的方法名即可。這里也需要注意傳參與返回必須一致。比如:
@Slf4j
@Service
public class TestService {
// 熔斷與降級處理
@SentinelResource(value = "doSomeThing2", fallback = "fallbackHandler")
public void doSomeThing2(String str) {
log.info(str);
throw new RuntimeException("發生異常");
}
public void fallbackHandler(String str) {
log.error("fallbackHandler:" + str);
}
}
完成上面的改造之后,重啟應用,並設置doSomeThing2
資源的熔斷降級策略(使用異常百分比),然后頻繁的請求/hello2
接口。在QPS>=5之后,由於這個接口一直在拋出異常,所以一定會滿足熔斷降級條件,這時候就會執行fallbackHandler
方法,不斷的打印如下日志:
2019-06-27 23:44:19.432 ERROR 58471 --- [nio-8001-exec-1] c.d.a.sentinel.service.TestService : fallbackHandler:hello2 Thu Jun 27 23:44:19 CST 2019
2019-06-27 23:44:19.599 ERROR 58471 --- [nio-8001-exec-2] c.d.a.sentinel.service.TestService : fallbackHandler:hello2 Thu Jun 27 23:44:19 CST 2019
2019-06-27 23:44:19.791 ERROR 58471 --- [nio-8001-exec-3] c.d.a.sentinel.service.TestService : fallbackHandler:hello2 Thu Jun 27 23:44:19 CST 2019
2019-06-27 23:44:19.975 ERROR 58471 --- [nio-8001-exec-4] c.d.a.sentinel.service.TestService : fallbackHandler:hello2 Thu Jun 27 23:44:19 CST 2019
2019-06-27 23:44:20.168 ERROR 58471 --- [nio-8001-exec-5] c.d.a.sentinel.service.TestService : fallbackHandler:hello2 Thu Jun 27 23:44:20 CST 2019
更多注解屬性說明
關於@SentinelResource
注解最主要的兩個用法:限流控制和熔斷降級的具體使用案例介紹完了。另外,該注解還有一些其他更精細化的配置,比如忽略某些異常的配置、默認降級函數等等,具體可見如下說明:
value
:資源名稱,必需項(不能為空)entryType
:entry 類型,可選項(默認為EntryType.OUT
)blockHandler
/blockHandlerClass
:blockHandler
對應處理BlockException
的函數名稱,可選項。blockHandler 函數訪問范圍需要是public
,返回類型需要與原方法相匹配,參數類型需要和原方法相匹配並且最后加一個額外的參數,類型為BlockException
。blockHandler 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定blockHandlerClass
為對應的類的Class
對象,注意對應的函數必需為 static 函數,否則無法解析。fallback
:fallback 函數名稱,可選項,用於在拋出異常的時候提供 fallback 處理邏輯。fallback 函數可以針對所有類型的異常(除了exceptionsToIgnore
里面排除掉的異常類型)進行處理。fallback 函數簽名和位置要求:- 返回值類型必須與原函數返回值類型一致;
- 方法參數列表需要和原函數一致,或者可以額外多一個
Throwable
類型的參數用於接收對應的異常。 - fallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定
fallbackClass
為對應的類的Class
對象,注意對應的函數必需為 static 函數,否則無法解析。
defaultFallback
(since 1.6.0):默認的 fallback 函數名稱,可選項,通常用於通用的 fallback 邏輯(即可以用於很多服務或方法)。默認 fallback 函數可以針對所有類型的異常(除了exceptionsToIgnore
里面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則只有 fallback 會生效。defaultFallback 函數簽名要求:- 返回值類型必須與原函數返回值類型一致;
- 方法參數列表需要為空,或者可以額外多一個
Throwable
類型的參數用於接收對應的異常。 - defaultFallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定
fallbackClass
為對應的類的Class
對象,注意對應的函數必需為 static 函數,否則無法解析。
exceptionsToIgnore
(since 1.6.0):用於指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣拋出。
注:1.6.0 之前的版本 fallback 函數只針對降級異常(
DegradeException
)進行處理,不能針對業務異常進行處理。
特別地,若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException
時只會進入 blockHandler
處理邏輯。若未配置 blockHandler
、fallback
和 defaultFallback
,則被限流降級時會將 BlockException
直接拋出。
參考資料:Sentinel官方文檔
版本說明:本文基於spring-cloud-alibaba-dependencies版本為0.2.2,如您遇到特殊問題,請先核對版本是否一致,或直接參考代碼示例核對具體案例。
代碼示例
本文介紹內容的客戶端代碼,示例讀者可以通過查看下面倉庫中的alibaba-sentinel-annotation
項目:
- Github:https://github.com/dyc87112/SpringCloud-Learning/
- Gitee:https://gitee.com/didispace/SpringCloud-Learning/
如果您對這些感興趣,歡迎star、follow、收藏、轉發給予支持!