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

在以前的《使用Sentinel實現接口限流》一文中,咱們僅依靠引入Spring Cloud Alibaba對Sentinel的整合封裝spring-cloud-starter-alibaba-sentinel,就完成了對全部Spring MVC接口的限流控制。然而,在實際應用過程當中,咱們可能須要限流的層面不只限於接口。可能對於某個方法的調用限流,對於某個外部資源的調用限流等都但願作到控制。呢麼,這個時候咱們就不得不手工定義須要限流的資源點,並配置相關的限流策略等內容了。前端

今天這篇咱們就來一塊兒學習一下,如何使用@SentinelResource註解靈活的定義控制資源以及如何配置控制策略。java

自定義資源點

下面的例子基於您已經引入了Spring Cloud Alibaba Sentinel爲基礎,若是您還不會這些,建議優先閱讀《使用Sentinel實現接口限流》git

第一步:在應用主類中增長註解支持的配置:github

@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邏輯層的某個方法爲例:spring

@Slf4j
@Service
public class TestService {

    @SentinelResource(value = "doSomeThing")
    public void doSomeThing(String str) {
        log.info(str);
    }

}

到這裏一個須要被保護的方法就定義完成了。下面咱們分別說說,定義了資源點以後,咱們如何實現不一樣的保護策略,包括:限流、降級等。後端

如何實現限流與熔斷降級

在定義了資源點以後,咱們就能夠經過Dashboard來設置限流和降級策略來對資源點進行保護了。同時,也能夠經過@SentinelResource來指定出現限流和降級時候的異常處理策略。下面,就來一塊兒分別看看限流和降級都是如何實現的。緩存

實現限流控制

第一步:在Web層調用這個被保護的方法:app

@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 / blockHandlerClassblockHandler對應處理 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 處理邏輯。若未配置 blockHandlerfallback 和 defaultFallback,則被限流降級時會將 BlockException 直接拋出

參考資料Sentinel官方文檔 > > 版本說明:本文基於spring-cloud-alibaba-dependencies版本爲0.2.2,如您遇到特殊問題,請先覈對版本是否一致,或直接參考代碼示例覈對具體案例。

代碼示例

本文介紹內容的客戶端代碼,示例讀者能夠經過查看下面倉庫中的alibaba-sentinel-annotation項目:

相關文章
相關標籤/搜索