diff --git a/spring/Spring Cloud/CircuitBreaker/CircuitBreaker.md b/spring/Spring Cloud/CircuitBreaker/CircuitBreaker.md index 6fb40f3..c82efe0 100644 --- a/spring/Spring Cloud/CircuitBreaker/CircuitBreaker.md +++ b/spring/Spring Cloud/CircuitBreaker/CircuitBreaker.md @@ -32,6 +32,12 @@ - [decorate and execute a functional interface](#decorate-and-execute-a-functional-interface-2) - [consume emitted RegistryEvents](#consume-emitted-registryevents-2) - [consume emitted RateLimiterEvents](#consume-emitted-ratelimiterevents) + - [Retry](#retry) + - [Create a RetryRegistry](#create-a-retryregistry) + - [Create and configure Retry](#create-and-configure-retry) + - [Decorate and execute a functional interface](#decorate-and-execute-a-functional-interface-3) + - [consume emitted RegistryEvents](#consume-emitted-registryevents-3) + - [use custom IntervalFunction](#use-custom-intervalfunction) # CircuitBreaker @@ -704,3 +710,155 @@ ReactorAdapter.toFlux(rateLimiter.getEventPublisher()) .filter(event -> event.getEventType() == FAILED_ACQUIRE) .subscribe(event -> logger.info(...)) ``` + +## Retry +### Create a RetryRegistry +和CircuitBreaker module类似,该module也提供了in-memory RetryRegistry,用于对Retry对象进行管理。 + +```java +RetryRegistry retryRegistry = RetryRegistry.ofDefaults(); +``` + +### Create and configure Retry +可以提供一个自定义的global RetryConfig,可以使用builder来创建RetryConfig。 + +RetryConfig支持如下配置: +- `maxAttempts`: + - `default value`: 3 + - `description`:该参数代表最大尝试次数(包含最初始的调用,最初始的调用为first attemp) +- `waitDuration`: + - `default value`: 500 [ms] + - `description`: 在多次retry attempts之间的固定等待间隔 +- `intervalFunction`: + - `defaultValue`: `numOfAttempts -> waitDuration` + - `description`: 该参数对应的function用于在失败后修改等待时间,默认情况下每次失败后等待时间是固定的,都是waitDuration +- `intervalBiFunction`: + - `default value`: `(int numOfAttempts, Either result)->waitDuration` + - `description`: 该function用于在failure后修改等待时间间隔,即基于attempt number和result/exception来计算等待时间间隔。当同时使用`intervalFunction`和`intervalBiFunction`时,会抛出异常 +- `retryOnResultPredicate`: + - `default value`: `result -> false` + - `description`: 该参数用于配置predicate,用于判断result是否应该被重试。如果result应当被重试,那么返回true,否则返回false +- `retryOnExceptionPredicate`: + - `default`: `throwable -> true` + - `description`: 该参数用于判断是否exception应当被重试。如果exception应当被重试,predicate返回true,否则返回false +- `retryExceptions`: + - `default value`: empty + - `description`: 配置exception list,其将被记录为failure并且应当被重试。 +- `ignoreExceptions`: + - `default value`: empty + - `descritpion`: 配置exception list,该列表中的异常会被ignore并且不会重试 +- `failAfterMaxAttempts`: + - `default value`: false + - `description`: 该参数用于启用和关闭`MaxRetriesExceededException`的抛出,当Retry达到配置的maxAttempts后,若result没有通过`retryOnResultPredicate`,则会根据`failAfterMaxAttempts`来重试 + +> 在抛出异常后,是否重试的逻辑如下: +> - 根据predicate判断该异常是否应该重试,predicate逻辑判断流程如下 +> - 如果异常位于ignoreExceptions中,则不应重试 +> - 如果异常位于retryExceptions中,则predicate返回为true +> - 如果异常不位于retryExceptions中,则根据retryOnExceptionPredicate来判断是否异常应当触发重试 +> - 如果上述的predicate判断异常应该被重试,那么再递增重试次数,判断当前重试是否超过maxAttempts +> - 如果没有超过,则在等待interval后触发重试 +> - 如果超过maxAttempts规定的上限,则不再重试直接抛出异常 + +> 在未抛出异常时,判断是否重试的逻辑如下: +> - 首先,根据`retryOnResultPredicate`判断当前返回结果是否应当触发重试,如果不应触发重试,则流程结束 +> - 如果应当触发重试,则增加当前的重试次数,并和maxAttempts进行比较 +> - 如果当前重试次数未超过maxAttempts,则在等待interval后触发重试 +> - 如果重试次数超过maxAttempts规定的值,那么将根据failAfterMaxAttempts来决定是否抛出异常。当failAfterMaxAttempts为true时,抛出异常;当为false时,不跑出异常。默认不会抛出异常。 + +创建RetryConfig的默认示例如下: +```java +RetryConfig config = RetryConfig.custom() + .maxAttempts(2) + .waitDuration(Duration.ofMillis(1000)) + .retryOnResult(response -> response.getStatus() == 500) + .retryOnException(e -> e instanceof WebServiceException) + .retryExceptions(IOException.class, TimeoutException.class) + .ignoreExceptions(BusinessException.class, OtherBusinessException.class) + .failAfterMaxAttempts(true) + .build(); + +// Create a RetryRegistry with a custom global configuration +RetryRegistry registry = RetryRegistry.of(config); + +// Get or create a Retry from the registry - +// Retry will be backed by the default config +Retry retryWithDefaultConfig = registry.retry("name1"); + +// Get or create a Retry from the registry, +// use a custom configuration when creating the retry +RetryConfig custom = RetryConfig.custom() + .waitDuration(Duration.ofMillis(100)) + .build(); + +Retry retryWithCustomConfig = registry.retry("name2", custom); +``` +### Decorate and execute a functional interface +Retry可以针对`callable, supplier, runnable, consumer, checkedrunnable, checkedsupplier, checkedconsumer, completionstage`进行decorate,使用示例如下: +```java +// Given I have a HelloWorldService which throws an exception +HelloWorldService helloWorldService = mock(HelloWorldService.class); +given(helloWorldService.sayHelloWorld()) + .willThrow(new WebServiceException("BAM!")); + +// Create a Retry with default configuration +Retry retry = Retry.ofDefaults("id"); +// Decorate the invocation of the HelloWorldService +CheckedFunction0 retryableSupplier = Retry + .decorateCheckedSupplier(retry, helloWorldService::sayHelloWorld); + +// When I invoke the function +Try result = Try.of(retryableSupplier) + .recover((throwable) -> "Hello world from recovery function"); + +// Then the helloWorldService should be invoked 3 times +BDDMockito.then(helloWorldService).should(times(3)).sayHelloWorld(); +// and the exception should be handled by the recovery function +assertThat(result.get()).isEqualTo("Hello world from recovery function"); +``` + +### consume emitted RegistryEvents +可以向RetryRegistry注册监听,消费Retry的create, replace, delete事件 +```java +RetryRegistry registry = RetryRegistry.ofDefaults(); +registry.getEventPublisher() + .onEntryAdded(entryAddedEvent -> { + Retry addedRetry = entryAddedEvent.getAddedEntry(); + LOG.info("Retry {} added", addedRetry.getName()); + }) + .onEntryRemoved(entryRemovedEvent -> { + Retry removedRetry = entryRemovedEvent.getRemovedEntry(); + LOG.info("Retry {} removed", removedRetry.getName()); + }); +``` + +### use custom IntervalFunction +如果不想使用fixed wait duration,可以自定义`IntervalFunction`,该函数可以在每次attempt时独立计算wait duration。resilience4j支持一些工厂方法用于创建IntervalFunction,示例如下 +```java +IntervalFunction defaultWaitInterval = IntervalFunction + .ofDefaults(); + +// This interval function is used internally +// when you only configure waitDuration +IntervalFunction fixedWaitInterval = IntervalFunction + .of(Duration.ofSeconds(5)); + +IntervalFunction intervalWithExponentialBackoff = IntervalFunction + .ofExponentialBackoff(); + +IntervalFunction intervalWithCustomExponentialBackoff = IntervalFunction + .ofExponentialBackoff(IntervalFunction.DEFAULT_INITIAL_INTERVAL, 2d); + +IntervalFunction randomWaitInterval = IntervalFunction + .ofRandomized(); + +// Overwrite the default intervalFunction with your custom one +RetryConfig retryConfig = RetryConfig.custom() + .intervalFunction(intervalWithExponentialBackoff) + .build(); +``` +> intervalFunction和intervalBiFunction不能同时指定,同时指定时会抛出异常。 +> +> 如果指定了intervalFunction,那么在通过builder创建RetryConfig时,会自动通过intervalFunction给intervalBiFunction也赋值。 +> +> 如果指定了intervalFunction或intervalBiFunction中任一,则使用指定的函数来计算waitDuration,当二者都没有指定时,则waitDuration固定为waitDuration \ No newline at end of file