天天看點

這個注解一次搞定限流與熔斷降級:@SentinelResource

在之前的 《使用Sentinel實作接口限流》 一文中,我們僅依靠引入Spring Cloud Alibaba對Sentinel的整合封裝

spring-cloud-starter-alibaba-sentinel

,就完成了對所有Spring MVC接口的限流控制。然而,在實際應用過程中,我們可能需要限流的層面不僅限于接口。可能對于某個方法的調用限流,對于某個外部資源的調用限流等都希望做到控制。呢麼,這個時候我們就不得不手工定義需要限流的資源點,并配置相關的限流政策等内容了。

今天這篇我們就來一起學習一下,如何使用

@SentinelResource

注解靈活的定義控制資源以及如何配置控制政策。

自定義資源點

下面的例子基于您已經引入了Spring Cloud Alibaba 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上可以看到如下圖所示的幾個控制點:

這個注解一次搞定限流與熔斷降級:@SentinelResource

可以看到,除了如之前入門執行個體中那樣有

/hello

資源點之外,多了一個

doSomeThing

資源點。可以通過界面為這個資源點設定限流規則,比如将其QPS設定為2。由于

/hello

資源不設定限流規則,是以隻要請求

/hello

接口,就可以直接模拟調用

doSomeThing

資源,來觀察限流規則是否生效。

下面可以通過任何你喜歡的工具來調用

/hello

接口,隻要QPS超過2,那麼就會出現如下的錯誤傳回,代表限流政策生效了。

這個注解一次搞定限流與熔斷降級:@SentinelResource

此時,服務端的控制台也會有對應的限流報錯日志:

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(秒)。

這個注解一次搞定限流與熔斷降級:@SentinelResource

第四步:驗證熔斷降級,根據上面的降級政策配置,當

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

  • defaultFallback

    (since 1.6.0):預設的 fallback 函數名稱,可選項,通常用于通用的 fallback 邏輯(即可以用于很多服務或方法)。預設 fallback 函數可以針對所有類型的異常(除了

    exceptionsToIgnore

    裡面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則隻有 fallback 會生效。defaultFallback 函數簽名要求:
    • 方法參數清單需要為空,或者可以額外多一個

      Throwable

    • defaultFallback 函數預設需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定

      fallbackClass

      Class

  • 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

項目:

如果您對這些感興趣,歡迎star、follow、收藏、轉發給予支援!