Resilience4j使用指南

案例概述

在本文中,咱們討論一下Resilience4j庫。
該庫經過管理遠程通訊的容錯性來幫助實現彈性系統。
這個庫受到Hystrix的啓發,但提供了更方便的API和許多其餘特性,如速率限制器(阻塞太頻繁的請求)、Bulkhead(避免太多併發請求)等。html

Maven設置

首先,咱們須要將目標模塊添加到咱們的pom.xml中(例如,咱們添加了Circuit Breaker):java

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

在這裏,咱們使用的是斷路器模塊。全部模塊及其最新版本都可在Maven Central上找到。
在接下來的部分中,咱們將介紹庫中最經常使用的模塊。git

斷路器

請注意,對於此模塊,咱們須要上面顯示的設置resilience4j-circuitbreaker依賴項。github

斷路器模式能夠幫助咱們在遠程服務中斷時防止一連串的故障。spring

在屢次失敗的嘗試以後,咱們能夠認爲服務不可用/重載,並急切地拒絕全部後續的請求。經過這種方式,咱們能夠爲可能失敗的調用節省系統資源。緩存

讓咱們看看咱們如何經過Resilience4j實現這一目標。服務器

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

CircuitBreakerRegistry circuitBreakerRegistry
  = CircuitBreakerRegistry.ofDefaults();

也可使用自定義參數:app

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

在這裏,咱們將速率閾值設置爲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));
斷路器的狀態和設置

斷路器能夠處於如下三種狀態之一:

  • CLOSED - 一切正常,不涉及短路
  • OPEN - 遠程服務器已關閉,全部請求都被短路
  • HALF_OPEN - 從進入開放狀態到如今已經通過了一段時間,斷路器容許請求檢查遠程服務是否從新上線

咱們能夠配置如下設置:

  • 斷路器打開並開始短路呼叫的故障率閾值
  • 等待時間,它定義了斷路器在切換到半開狀態以前應該保持打開狀態的時間
  • 斷路器半開或半閉時環形緩衝器的尺寸
  • 處理斷路器事件的定製電路斷路器事件監聽器
  • 一個自定義謂詞,用於評估異常是否應算做故障,從而提升故障率
速率限制器

與上一節相似,此功能須要resilience4j-ratelimiter依賴項。

顧名思義,此功能容許限制對某些服務的訪問。它的API與CircuitBreaker很是類似- 有Registry,Config和Limiter類。

如下是它的示例:

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);

如今,若是須要的話,全部對已修飾的服務塊的調用都要符合速率限制器配置。

咱們能夠配置以下參數:

  • 限制刷新的時間段
  • 刷新週期的權限限制
  • 默認等待權限持續時間
Bulkhead

在這裏,咱們首先須要relasticience4j-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);

要測試此配置,咱們將調用模擬服務的方法。

而後,咱們確保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();

咱們能夠配置如下設置:

  • Bulkhead容許的最大並行執行量
  • 嘗試進入飽和艙壁時線程等待的最長時間
重試

對於此功能,咱們須要將resilience4j-retry庫添加到項目中。

咱們可使用Retry API 自動重試失敗的呼叫

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));
}

咱們還能夠配置如下內容:

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

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僅支持 Supplier和Callable類型。

TimeLimiter

對於此模塊,咱們必須添加resilience4j-timelimiter依賴項。
可使用TimeLimiter限制調用遠程服務所花費的時間
爲了演示,讓咱們設置一個配置超時爲1毫秒的TimeLimiter:

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

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

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);
附加模塊

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

一些比較知名的集成是:

  • Spring Boot – resilience4j-spring-boot
  • Ratpack – resilience4j-ratpack
  • Retrofit – resilience4j-retrofit
  • Vertx – resilience4j-vertx
  • Dropwizard – resilience4j-metrics
  • Prometheus – resilience4j-prometheus
案例結論

在本文中,咱們瞭解了Resilience4j庫的各個方面,並學習瞭如何使用它來解決服務器間通訊中的各類容錯問題。

相關文章
相關標籤/搜索