案例概述
在本文中,咱們討論一下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));
斷路器的狀態和設置
斷路器能夠處於如下三種狀態之一:
咱們能夠配置如下設置:
速率限制器
與上一節相似,此功能須要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();
咱們能夠配置如下設置:
重試
對於此功能,咱們須要將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還提供了許多附加模塊,能夠簡化與流行框架和庫的集成。
一些比較知名的集成是:
案例結論
在本文中,咱們瞭解了Resilience4j庫的各個方面,並學習瞭如何使用它來解決服務器間通訊中的各類容錯問題。