分佈式系統的三個斷路器框架的原理和實踐

分佈式系統的三個斷路器框架的原理和實踐

springboot實戰電商項目mall4j (https://gitee.com/gz-yami/mall4j)java

java開源商城系統git

​ 隨着微服務的流行,熔斷做爲其中一項很重要的技術也廣爲人知。當微服務的運行質量低於某個臨界值時,啓動熔斷機制,暫停微服務調用一段時間,以保障後端的微服務不會由於持續過負荷而宕機。本文介紹了Hystrix、新一代熔斷器Resilience4j以及阿里開源的Sentinel如何使用。若有錯誤歡迎指出。github

1. 爲何須要斷路器

​ 斷路器模式源於Martin Fowler的Circuit Breaker 一文。「斷路器」自己是一種開關裝置,用於在電路上保護線路過載,當線路中有電器發生短路時,「斷路器」可以及時切斷故障電路,防止發生過載、發熱甚至起火等嚴重後果。spring

​ 在分佈式架構中,斷路器模式的做用也是相似的,當某個服務單元發生故障(相似用電器發生短路)以後,經過斷路器的故障監控(相似熔斷保險絲),向調用方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得線程因調用故障服務被長時間佔用不釋放,避免了故障在分佈式系統中的蔓延。編程

​ 針對上述問題,斷路器是進行實現了斷路、線程隔離、流量控制等一系列服務保護功能的框架。系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。後端

2. Hystrix

2.1什麼是Hystrix

​ Hystrix是一款Netfix開源的框架,具備依賴隔離,系統容錯降級等功能,這也是其最重要的兩種用途,還有請求合併等功能。緩存

2.2 Hystrix簡單案例

2.2.1 新建一個hystrix工程引入依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

2.2.2 在啓動類的上加上註解@EnableCircuitBreaker //啓用斷路器

@EnableCircuitBreaker
public class TestApplication extends SpringBootServletInitializer{

   public static void main(String[] args) {
        SpringApplication.run(ApiApplication.class, args);
   }
}

2.2.3 在 TestProductController中加入斷路邏輯

@RequestMapping("/get/{id}")
    @HystrixCommand(fallbackMethod="errorCallBack")   //測試沒有這個數據時,服務降級
  public Object get(@PathVariable("id") long id){
        Product p= productService.findById(id);
        if( p==null){
            throw new RuntimeException("查無此商品");
        }
        return p;
    }

    //指定一個降級的方法
    public Object errorCallBack(  @PathVariable("id") long id   ){
        return id+"不存在,error";
    }

2.3 總結

​ 簡單介紹了Hystrix工做原理以及簡單案例,不過Hystrix官方已經中止開發,就不深刻介紹了。springboot

3. Resilience4j

3.1 簡介

​ 在Hystrix官方已經中止開發後,Hystrix官方推薦使用新一代熔斷器爲Resilience4j。Resilience4j是一款輕量級,易於使用的容錯庫,其靈感來自於Netflix Hystrix,可是專爲Java 8和函數式編程而設計。由於庫只使用了Vavr(之前稱爲 Javaslang ),它沒有任何其餘外部依賴下。相比之下,Netflix Hystrix對Archaius具備編譯依賴性,Archaius具備更多的外部庫依賴性,例如Guava和Apache Commons Configuration,若是須要使用Resilience4j,也無需引入全部依賴,只需選擇你須要的功能模塊便可。服務器

3.2 模塊構成

Resilience4j提供了幾個核心模塊:架構

resilience4j-circuitbreaker:電路斷開
 resilience4j-ratelimiter:速率限制
 resilience4j-bulkhead:隔板
 resilience4j-retry:自動重試(同步和異步)
 resilience4j-timelimiter:超時處理
 resilience4j-cache:結果緩存

3.3 設置Maven

引入依賴

<dependency>
         <groupId>io.github.resilience4j</groupId>
         <artifactId>resilience4j-circuitbreaker</artifactId>
         <version>0.13.2</version>
  </dependency>

3.4 斷路器(CircuitBreaker

請注意,使用此功能,咱們須要引入上文的resilience4j-circuitbreaker依賴。

該熔斷器模式下能夠幫助咱們在遠程服務出故障時防止故障級聯。

在屢次請求失敗後,咱們就認爲服務不可用/超載,而且對以後的全部請求進行短路處理,這樣咱們就能節約系統資源。讓咱們看看如何經過Resilience4j實現這一目標。

首先,咱們須要定義要使用的設置。最簡單的方法是使用默認設置:

CircuitBreakerRegistry circuitBreakerRegistry  = CircuitBreakerRegistry.ofDefaults();

一樣也可使用自定義參數:

CircuitBreakerConfig config = CircuitBreakerConfig.custom()
  .failureRateThreshold(20)
  .ringBufferSizeInClosedState(5)
  .build();

在這裏,咱們將ratethreshold設置爲20%,而且最少重試5次。

而後,咱們建立一個 CircuitBreaker對象,並經過它調用遠程服務:

interface RemoteService {
    int process(int i);
}

CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
CircuitBreaker circuitBreaker = registry.circuitBreaker("my");
Function<Integer, Integer> decorated = CircuitBreaker
  .decorateFunction(circuitBreaker, service::process);

最後,讓咱們看看它如何經過JUnit測試。

咱們調用服務10次。能夠驗證服務至少調用5次,若是有20%的失敗的狀況下,會中止調用。

when(service.process(any(Integer.class))).thenThrow(new RuntimeException());

for (int i = 0; i < 10; i++) {
    try {
        decorated.apply(i);
    } catch (Exception ignore) {}
}

verify(service, times(5)).process(any(Integer.class));

斷路器的三種狀態:

  • 關閉— 服務正常,不涉及短路
  • 打開— 遠程服務宕機,全部請求都短路
  • 半開— 進入打開狀態一段時間(根據已配置的時間量)後,熔斷器容許檢查遠程服務是否恢復

咱們能夠配置如下設置:

  • 故障率閾值,高於該閾值時CircuitBreaker 打開
  • 等待時間,用於定義CircuitBreaker切換爲半開狀態以前應保持打開狀態的時間
  • CircuitBreaker半開或閉合時,環形緩衝區的大小
  • 處理自定義事件的的監聽器CircuitBreakerEventListener,它處理CircuitBreaker事件
  • 自定義謂詞,用於評估異常是否爲失敗,從而提升失敗率

3.5 限流器

此功能須要使用resilience4j-ratelimiter依賴性。

簡單示例:

RateLimiterConfig config = RateLimiterConfig.custom().limitForPeriod(2).build();
RateLimiterRegistry registry = RateLimiterRegistry.of(config);
RateLimiter rateLimiter = registry.rateLimiter("my");
Function<Integer, Integer> decorated
  = RateLimiter.decorateFunction(rateLimiter, service::process);

如今全部對decorateFunction的調用都符合rate limiter。

咱們能夠配置以下參數:

  • 極限刷新時間
  • 刷新期間的權限限制
  • 默認等待許可期限

3.6 艙壁隔離

這裏須要引入resilience4j-bulkhead依賴,能夠限制對特定服務的併發調用數。

讓咱們看一個使用Bulkhead API配置併發調用的示例:

BulkheadConfig config = BulkheadConfig.custom().maxConcurrentCalls(1).build();
BulkheadRegistry registry = BulkheadRegistry.of(config);
Bulkhead bulkhead = registry.bulkhead("my");
Function<Integer, Integer> decorated
  = Bulkhead.decorateFunction(bulkhead, service::process);

爲了測試,咱們能夠調用一個mock服務的方法。這種狀況下,咱們就確保Bulkhead不容許其餘任何調用:

CountDownLatch latch = new CountDownLatch(1);
when(service.process(anyInt())).thenAnswer(invocation -> {
    latch.countDown();
    Thread.currentThread().join();
    return null;
});

ForkJoinTask<?> task = ForkJoinPool.commonPool().submit(() -> {
    try {
        decorated.apply(1);
    } finally {
        bulkhead.onComplete();
    }
});
latch.await();
assertThat(bulkhead.isCallPermitted()).isFalse();

咱們能夠配置如下設置:

  • 容許的最大並行數
  • 進入飽和艙壁時線程將等待的最大時間

3.7 重試

須要引入resilience4j-retry庫。可使用Retry調用失敗後自動重試:

RetryConfig config = RetryConfig.custom().maxAttempts(2).build();
RetryRegistry registry = RetryRegistry.of(config);
Retry retry = registry.retry("my");
Function<Integer, Void> decorated
  = Retry.decorateFunction(retry, (Integer s) -> {
        service.process(s);
        return null;
    });

如今,讓咱們模擬在遠程服務調用期間引起異常的狀況,並確保庫自動重試失敗的調用:

when(service.process(anyInt())).thenThrow(new RuntimeException());
try {
    decorated.apply(1);
    fail("Expected an exception to be thrown if all retries failed");
} catch (Exception e) {
    verify(service, times(2)).process(any(Integer.class));
}

咱們還能夠配置:

  • 最大嘗試次數
  • 重試前的等待時間
  • 自定義函數,用於修改失敗後的等待間隔
  • 自定義謂詞,用於評估異常是否會致使重試調用

3.8 緩存

cache模塊須要引入resilience4j-cache依賴。初始化代碼以下:

javax.cache.Cache cache = ...; // Use appropriate cache here
Cache<Integer, Integer> cacheContext = Cache.of(cache);
Function<Integer, Integer> decorated
  = Cache.decorateSupplier(cacheContext, () -> service.process(1));

這裏的緩存是經過 JSR-107 Cache實現完成的,Resilience4j提供了操做緩存的方法。

請注意,沒有用於裝飾方法的API(例如Cache.decorateFunction(Function)),該API僅支持 SupplierCallable類型。

3.9 限時器

對於此模塊,咱們須要引入resilience4j-timelimiter依賴,能夠限制使用TimeLimiter調用遠程服務所花費的時間。

咱們設置一個TimeLimiter,配置的超時時間爲1毫秒以方便測試:

long ttl = 1;
TimeLimiterConfig config
  = TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(ttl)).build();
TimeLimiter timeLimiter = TimeLimiter.of(config);

接下來,讓咱們調用Future.get()驗證Resilience4j是否如預期超時:

Future futureMock = mock(Future.class);
Callable restrictedCall
  = TimeLimiter.decorateFutureSupplier(timeLimiter, () -> futureMock);
restrictedCall.call();

verify(futureMock).get(ttl, TimeUnit.MILLISECONDS);

咱們也能夠將其與斷路器(CircuitBreaker)結合使用:

Callable chainedCallable
  = CircuitBreaker.decorateCallable(circuitBreaker, restrictedCall);

3.10 附加模塊

Resilience4j還提供了許多附加的功能模塊,可簡化其與流行框架和庫的集成。

一些比較常見的集成是:

  • Spring Boot – resilience4j-spring-boot模塊
  • RatpackResilience4j-ratpack模塊
  • Retrofit – resilience4j-Retrofit模塊
  • Vertx – Resilience4j-vertx模塊
  • Dropwizard – Resilience4j-metrics模塊
  • Prometheus – resilience4j-prometheus模塊

3.11 總結

​ 經過上文咱們瞭解了Resilience4j庫的各個方面的簡單使用,以及如何使用它來解決服務器間通訊中的各類容錯問題。Resilience4j的源碼能夠在GitHub上找到。

4. Sentinel

4.1 什麼是Sentinel?

​ Sentinel 是面向分佈式服務架構的輕量級流量控制組件,由阿里開源,主要以流量爲切入點,從限流、流量整形、熔斷降級、系統負載保護等多個維度來保障微服務的穩定性。

4.2 Sentinel 具備如下特性:

  • 豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量能夠承受的範圍)、消息削峯填谷、集羣流量控制、實時熔斷下游不可用應用等。
  • 完備的實時監控:Sentinel 同時提供實時的監控功能。您能夠在控制檯中看到接入應用的單臺機器秒級數據,甚至 500 臺如下規模的集羣的彙總運行狀況。
  • 普遍的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只須要引入相應的依賴並進行簡單的配置便可快速地接入 Sentinel。
  • 完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您能夠經過實現擴展接口來快速地定製邏輯。例如定製規則管理、適配動態數據源等。

4.3 工做機制:

  • 對主流框架提供適配或者顯示的 API,來定義須要保護的資源,並提供設施對資源進行實時統計和調用鏈路分析。
  • 根據預設的規則,結合對資源的實時統計信息,對流量進行控制。同時,Sentinel 提供開放的接口,方便您定義及改變規則。
  • Sentinel 提供實時的監控系統,方便您快速瞭解目前系統的狀態。

4.4 Sentinel總結:

​ Sentinel 是面向分佈式服務架構的高可用流量防禦組件,做爲阿里的熔斷中間件,Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,對於流量防禦的高可用、穩定性方面是很突出的。

5.總結

三種主流熔斷中間件的性能對比,如表所示:

圖表

springboot實戰電商項目mall4j (https://gitee.com/gz-yami/mall4j)

java開源商城系統

相關文章
相關標籤/搜索