SpringCloud 中集成Sentinel+Feign實現服務熔斷降級

Sentine

1.背景

Sentinel 是阿里中間件團隊開源的,面向分佈式服務架構的輕量級高可用流量控制組件,主要以流量爲切入點,從流量控制、熔斷降級、系統負載保護等多個維度來幫助用戶保護服務的穩定性。這裏你們可能會問:Sentinel 和以前經常使用的熔斷降級庫 Netflix Hystrix 有什麼異同呢?Sentinel官網有一個對比和Hystrix遷移到sentinel的文章,這裏摘抄一個總結的表格,具體的對比能夠點此 連接 查看。 java

功能對比

 從對比的表格能夠明顯看到,Sentinel比Hystrix在功能性上還要強大一些。git

2.功能

 Sentinel 功能主要體如今三個方面github

2.1 流量控制

    對於系統來講,任意時間到來的請求每每是隨機不可控的,而系統的處理能力是有限的。咱們須要根據系統的處理能力對流量進行控制。 web

控制角度以下:spring

  • 資源的調用關係,例如資源的調用鏈路,資源和資源之間的關係
  •  運行指標,例如 QPS、線程池、系統負載等
  •  控制的效果,例如直接限流、冷啓動、排隊等

2.2 熔斷降級

        當檢測到調用鏈路中某個資源出現不穩定的表現,例如請求響應時間長或異常比例升高的時候,則對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而致使級聯故障。手段以下json

  • 經過併發線程數進行限制 :當線程數在特定資源上堆積到必定的數量以後,對該資源的新請求就會被拒絕。堆積的線程完成任務後纔開始繼續接收請求。
  • 經過響應時間對資源進行降級:當依賴的資源出現響應時間過長後,全部對該資源的訪問都會被直接拒絕,直到過了指定的時間窗口以後才從新恢復。

2.3 系統負載保護  

          Sentinel 同時提供系統維度的自適應保護能力。防止雪崩,是系統防禦中重要的一環。當系統負載較高的時候,若是還持續讓請求進入,可能會致使系統崩潰,沒法響應。在集羣環境下,網絡負載均衡會把本應這臺機器承載的流量轉發到其它的機器上去。若是 這個時候其它的機器也處在一個邊緣狀態的時候,這個增長的流量就會致使這臺機器也崩潰,最後致使整個集羣不可用。api

          針對這個狀況,Sentinel 提供了對應的保護機制,讓系統的入口流量和系統的負載達到一個平衡,保證系統在能力範圍以內處理最多的請求。markdown

3.使用

3.1 依賴

    這裏我使用sentinel 是基於gradle配置,兼容spring clould alibaba,因此添加以下依賴網絡

compile'com.alibaba.cloud:spring-cloud-starter-alibaba-sentinel:2.1.0.RELEASE'
compile group: 'com.alibaba.csp', name: 'sentinel-transport-simple-http', version: '1.6.3'

3.2 註解

      Sentinel 提供了 @SentinelResource 註解用於定義資源,並提供了 AspectJ 的擴展用於自動定義資源、處理 BlockException等,固然也支持使用aop的方式,這裏演示使用aop的方式,添加以下配置類架構

@Configuration
public class SentinelAspectConfiguration {
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
}
}

 @SentinelResource 用於定義資源,並提供可選的異常處理和 fallback 配置項 。@SentinelResource 註解包含如下屬性

  • value:資源名稱,必需項(不能爲空)
  • entryType:entry 類型,可選項EntryType.OUT/EntryType.IN(默認爲 EntryType.OUT),對應入口控制/出口控制
  • blockHandler / blockHandlerClass: blockHandler 對應處理 BlockException 的函數名稱。
  • fallback:fallback 函數名稱,可選項,用於在拋出異常的時候提供 fallback 處理邏輯。fallback 函數能夠針對全部類型的異常(除了 exceptionsToIgnore 裏面排除掉的異常類型)進行處理
    • 返回值類型必須與原函數返回值類型一致
    • fllback 函數默認須要和原方法在同一個類中。若但願使用其餘類的函數,則能夠指定 fallbackClass 爲對應的類的 Class 對象,注意對應的函數必需爲 static 函數,不然沒法解析。
  • defaultFallback(since 1.6.0):默認的 fallback 函數名稱,可選項,一般用於通用的 fallback 邏輯(便可以用於不少服務或方法)。默認 fallback 函數能夠針對全部類型的異常(除了 exceptionsToIgnore 裏面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則只有 fallback 會生效。函數簽名和fallback一致
  • exceptionsToIgnore(since 1.6.0):用於指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣拋出。

3.3 示例  

服務具體實現類

@Service
@Slf4j
public class HelloProviderServiceImpl implements HelloProviderService {

  @Autowired
  private ConfigurableEnvironment configurableEnvironment;

  // 對應的 `handleException` 函數須要位於 `ExceptionUtil` 類中,而且必須爲 static 函數
  @Override
  @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {
      ExceptionUtil.class})
  public void test() {
    log.info("Test");
  }

  @Override
  @SentinelResource(value = "sayHi", blockHandler = "exceptionHandler", fallback = "helloFallback")
  public String sayHi(long time) {
    if (time < 0) {
      throw new IllegalArgumentException("invalid arg");
    }
    try {
      Thread.sleep(time);
    } catch (InterruptedException e) {
      throw new IllegalArgumentException("inter arg");
    }
    return String.format("Hello time %d", time);
  }

// 這裏俗稱資源埋點,在設置限流策略的時候會根據此埋點來控制 @Override @SentinelResource(value
= "helloAnother", defaultFallback = "defaultFallback", exceptionsToIgnore = {IllegalStateException.class}) public String helloAnother(String name) { if (name == null || "bad".equals(name)) { throw new IllegalArgumentException("oops"); } if ("foo".equals(name)) { throw new IllegalStateException("oops"); } return "Hello, " + name; } // Fallback 函數,函數簽名與原函數一致或加一個 Throwable 類型的參數. public String helloFallback(long s, Throwable ex) { log.error("fallbackHandler:" + s); return "Oops fallbackHandler, error occurred at " + s; } //默認的 fallback 函數名稱 public String defaultFallback() { log.info("Go to default fallback"); return "default_fallback"; } // Block 異常處理函數,參數最後多一個 BlockException,其他與原函數一致. public String exceptionHandler(long s, BlockException ex) { // Do some log here. return "Oops,exceptionHandler, error occurred at " + s; } }

服務接口

public interface HelloProviderService {
    public String sayHi(long t) throws InterruptedException;
    String helloAnother(String name);
    void test();
}
ExceptionUtil類
@Slf4j
public final class ExceptionUtil {
  public static void handleException(BlockException ex) {
     log.info("Oops: " + ex.getClass().getCanonicalName());
  }
}

controller 類

@RestController
@Slf4j
public class HelloProviderController {

  @Autowired
  HelloProviderServiceImpl helloServiceProviderService;

  @GetMapping("/sayHi")
  public String sayHi(@RequestParam(required = false) Long time) throws Exception {
    if (time == null) {
      time = 300L;
    }
    helloServiceProviderService.test();
    
return helloServiceProviderService.sayHi(time); } @GetMapping("baz/{name}") public String apiBaz(@PathVariable("name") String name) { return helloServiceProviderService.helloAnother(name); } }

3.4 Sentinel 控制檯

一個輕量級的開源控制檯,它提供機器發現以及健康狀況管理、監控(單機和集羣),規則管理和推送的功能。主要能夠經過該控制檯對服務端設置的資源埋點進行動態的限流配置推送,這樣能夠靈活的設置限流策略而不用在代碼裏寫死

  • 提供web界面,可視化資源和流量監控、對資源埋點進行配置
  • 具體安裝比較簡單,因此這裏再也不說起,能夠參考連接 

3.5 降級策略

  • 平均響應時間 (DEGRADE_GRADE_RT):當 1s 內持續進入 5 個請求,對應時刻的平均響應時間(秒級)均超過閾值(count,以 ms 爲單位),那麼在接下的時間窗口(DegradeRule 中的 timeWindow,以 s 爲單位)以內,對這個方法的調用都會自動地熔斷(拋出 DegradeException)。注意 Sentinel 默認統計的 RT 上限是 4900 ms,超出此閾值的都會算做 4900 ms,若須要變動此上限能夠經過啓動配置項 -Dcsp.sentinel.statistic.max.rt=xxx 來配置。

  • 異常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):當資源的每秒請求量 >= 5,而且每秒異常總數佔經過量的比值超過閾值(DegradeRule 中的 count)以後,資源進入降級狀態,即在接下的時間窗口(DegradeRule 中的 timeWindow,以 s 爲單位)以內,對這個方法的調用都會自動地返回。異常比率的閾值範圍是 [0.0, 1.0],表明 0% - 100%。

  • 異常數 (DEGRADE_GRADE_EXCEPTION_COUNT):當資源近 1 分鐘的異常數目超過閾值以後會進行熔斷。注意因爲統計時間窗口是分鐘級別的,若 timeWindow 小於 60s,則結束熔斷狀態後仍可能再進入熔斷狀態。

  • 能夠啓用Sentinel 控制檯,在控制檯上直接配置熔斷降級規則。

    • 打開控制檯界面,點擊簇點鏈路,選擇程序裏的資源埋點,點擊降級
    • 配置降級規則
    • 配置RT模式測試,控制檯輸入RT和窗口時間
      • url:ip:port/sayHi?time=delayTime, 當 1s 內持續進入 5 個請求 平均delayTime>RT 進入降級服務
    • 配置異常比例,控制檯輸入異常比例
      • url:ip:port/baz/bad, 當資源的每秒請求量 >= 5,而且每秒異常總數佔經過量的比值設定的異常比例 將在接下來設置的窗口時間內進入降級服務

Feign

  1. 背景 

    Feign是Netflix公司開源的輕量級的一種負載均衡的HTTP客戶端,,使用Feign調用API就像調用本地方法同樣,從避免了 調用目標微服務時,須要不斷的解析/封裝json 數據的繁瑣。 Spring Cloud引入Feign而且集成了Ribbon實現客戶端負載均衡調用。 通俗一點講:能夠像調用本地方法同樣的調用遠程服務的方法。
固然其中也有很多坑等踩。

   2.使用

 Sentinel 適配了 Fegin組件。若是想使用,除了引入 spring-cloud-starter-alibaba-sentinel 的依賴外還須要 2 個步驟:

  • 配置文件打開 Sentinel 對 Feign 的支持:feign.sentinel.enabled=true

  • 加入 openfeign starter 依賴使 sentinel starter 中的自動化配置類生效:
    compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '2.1.3.RELEASE'

2.1示例

    添加接口 EchoService類,該接口經過@FeignClient(name = "service-provider")註解來綁定該接口對應service01服務

@FeignClient(name = "nacos-provider-sentianel1", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
public interface EchoService {

  @GetMapping(value = "/sayHi")
  String sayHi(@RequestParam(value = "time", required = false) Long time);

  @RequestMapping("/api/{name}")
  String apiBaz(@PathVariable("name") String name);
}

    其中 @FeignClient 中name 中的值做爲 提供服務提供方的名稱,該接口中配置當前服務須要調用nacos-provider-sentianel1服務提供的接口。nacos-provider-sentianel1註冊到註冊服務上,我這裏使用的是Nacos.

   服務配置以下

  nacos-provider-sentianel1 中的controller是這個樣子的,這裏能夠看到 和EchoService中的方法簽名都是一致的

@RestController
public class HelloProviderController2 { @GetMapping("/echo") public String helloConsumer(@RequestParam(required = false) Long time) { return "echo"; } @GetMapping("/api/{name}") public String apiBaz(@PathVariable("name") String name) { return "another provider " + name; } }

添加 EchoServiceFallback,這裏是fegin的Fallback機制,主要用來作容錯處理。由於在網絡請求時,可能會出現異常請求,若是還想再異常狀況下使系統可用,那麼就須要容錯處理。

@Component。
public class EchoServiceFallback implements EchoService {

  @Override
  public String sayHi(Long time) {
    return "sayHi fallback";
  }

  @Override
  public String apiBaz(String name) {
    return "apiBaz fallback";
  }
}

添加FeignConfiguration 

@Configuration
public class FeignConfiguration {
  @Bean
  public EchoServiceFallback echoServiceFallback() {
    return new EchoServiceFallback();
  }
}

  在上文HelloProviderServiceImpl的基礎上添加EchoService調用

@Service
@Slf4j
public class HelloProviderServiceImpl implements HelloProviderService {

  @Autowired
  private ConfigurableEnvironment configurableEnvironment;

  @Autowired
  EchoService echoService;

  // 對應的 `handleException` 函數須要位於 `ExceptionUtil` 類中,而且必須爲 static 函數
  @Override
  @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {
      ExceptionUtil.class})
  public void test() {
    log.info("Test");
  }

  @Override
  @SentinelResource(value = "sayHi", blockHandler = "exceptionHandler", fallback = "helloFallback")
  public String sayHi(long time) {
    if (time < 0) {
      throw new IllegalArgumentException("invalid arg");
    }
    try {
      Thread.sleep(time);
    } catch (InterruptedException e) {
      throw new IllegalArgumentException("inter arg");
    }
    return String.format("Hello time %d", time);
  }

  @Override
  @SentinelResource(value = "helloAnother", defaultFallback = "defaultFallback",
      exceptionsToIgnore = {IllegalStateException.class})
  public String helloAnother(String name) {
    if (name == null || "bad".equals(name)) {
      throw new IllegalArgumentException("oops");
    }
    if ("foo".equals(name)) {
      throw new IllegalStateException("oops");
    }
    return "Hello, " + name;
  }

  // Fallback 函數,函數簽名與原函數一致或加一個 Throwable 類型的參數.
  public String helloFallback(long s, Throwable ex) {
    log.error("fallbackHandler:" + s);

    return "Oops fallbackHandler, error occurred at " + s;
  }

  //默認的 fallback 函數名稱
  public String defaultFallback() {
    log.info("Go to default fallback");
    return echoService.apiBaz("bad");
    //return "default_fallback";
  }

  // Block 異常處理函數,參數最後多一個 BlockException,其他與原函數一致.
  public String exceptionHandler(long s, BlockException ex) {
    // Do some log here.
    return "Oops,exceptionHandler, error occurred at " + s;
  }
}

  這裏咱們在defaultFallback中使用 echoService.apiBaz("bad") 來調用nacos-provider-sentianel1 的apiBaz方法

  在sentinel控制檯中配置helloAnother的降級規則,當觸發降級後,將會調用acos-provider-sentianel1服務的apiBaz方法,返回結果。

總結

      使用sentinel控制系統流量,當系統流超出當前服務的接受範圍的時候,能夠經過Feign 調用降級服務,這樣就可構成一個最基礎的熔斷降級模塊,固然Feign中還集成了Ribbon,能夠經過配置實現客戶端負載均衡調用。

相關文章
相關標籤/搜索