基于 resilience4j的项目重试方案
一、背景
项目中存在一些依赖,由于不稳定,偶尔会发生超时或请求失败的情况。
比如:
1、查询hive
2、brpc、http调用三方接口
二、目标
核心功能:能够重试以及重试策略可配置
三、可行方案
方案一:resilience4j-retry
重试实例: Retry
重试注册器:RetryRegistry
重试配置类:RetryConfig、IntervalBiFunction
事件回调相关: RegistryEventConsumer
方案二:Guava Retry
重试实例: Retryer
重试注册器:RetryerBuilder
重试配置类:StopStrategy、WaitStrategy、BlockStrategy
事件回调相关: RetryListener
方案三:Spring Retry
重试实例: RetryOperations
重试注册器:RetryTemplate
重试配置类:RetryPolicy
事件回调相关: RetryListener
方案四:Smart Retry
重试实例: RetryHandler
重试注册器:RetryHandlerRegistration
重试配置类:RetryHandler
事件回调相关: RetryListener
特点:支持持久化的任务重试,保证数据的一致性
适用场景
* 方法重试需要持久化,系统重启、宕机恢复之后继续重试,直到重试成功
* 分布式事务最终一致性
不适用场景
* 延时低、实时性很高的重试
* 允许任务丢失(直接用Guava Retry就行了)
* 需要同步重试的
四、确定最终方案
四种方案对比:
类型 | 同步、异步 | 是否支持声明式调用(注解) | 是否支持监控 |
resilience4j-retry | 同步 | 是 | 是 |
Guava Retry | 同步 | 否 | 否,可通过监听器自行实现监控统计 |
Spring Retry | 同步 | 是 | 否,可通过监听器自行实现监控统计 |
Smart Retry | 异步 | 是 | 否,可通过监听器自行实现监控统计 |
基于以上方案的对比,选择了使用resilience4j-retry,主要基于以下两点:
1)本身提供了监控数据,可完美接入premethus
2)resilience4j除了提供重试能力,还具备Hystrix相同的能力,包括断路器、隔断、限流、缓存。提供与Spring Boot集成的依赖,大大简化了集成成本。(后期可考虑从Hystrix迁移到resilience4j)
与Hystrix相比的优势:
1、Resilience4j是一个受Netflix Hystrix启发的轻量级容错库,轻量级因为这个库只使用Vavr,而Vavr没有任何其他外部库依赖
2、专为Java 8和函数式编程而设计,使用更方便
五、方案落地
1、resilience4j核心步骤介绍:
1)创建RetryConfig,来对Retry进行配置
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();
|
2)创建IntervalBiFunction,用于定义重试间隔时间策略
IntervalBiFunction<Object> intervalBiFunction = IntervalBiFunction.ofIntervalFunction(attempt -> Double.valueOf(attempt * DEFAULT_INITIAL_INTERVAL * DEFAULT_MULTIPLIER).longValue());
RetryConfig config = RetryConfig.custom()
.intervalBiFunction(intervalBiFunction)
.build();
|
3)基于RetryConfig创建RetryRegistry,来注册Retry
RetryRegistry registry = RetryRegistry.of(config);
|
4)通过Retry来实现业务重试
// 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<String> retryableSupplier = Retry
.decorateCheckedSupplier(retry, helloWorldService::sayHelloWorld);
// When I invoke the function
Try<String> 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");
|
5) 可订阅消费RegistryEvents,包括Retry的创建、替换、删除
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());
});
|
2、Spring Boot集成三部曲(如果spring boot版本为2.0及以上的,使用resilience4j-spring-boot2依赖集成更方便)
1)定义Spring Boot配置类:
@Configuration
@Slf4j
public class RetryAutoConfiguration {
@Autowired
PrometheusMeterRegistry prometheusMeterRegistry;
// 750、1500、2250、3000
private static final long DEFAULT_INITIAL_INTERVAL = 500;
private static final double DEFAULT_MULTIPLIER = 1.5;
private static final int DEFAULT_MAX_ATTEMPTS = 5;
private static final RetryConfig longConfig = RetryConfig.custom()
.maxAttempts(DEFAULT_MAX_ATTEMPTS)
.waitDuration(Duration.ofMillis(IntervalFunction.DEFAULT_INITIAL_INTERVAL))
.retryOnException(e -> e instanceof Throwable)
.retryExceptions(Throwable.class)
.ignoreExceptions(OpException.class)
// 设置重试间隔规则
.intervalBiFunction(IntervalBiFunction.ofIntervalFunction(attempt -> Double.valueOf(attempt * DEFAULT_INITIAL_INTERVAL * DEFAULT_MULTIPLIER).longValue()))
.build();
@Bean
public RetryRegistry retryRegistry() {
RetryRegistry registry = RetryRegistry.of(longConfig);
// Register all retries at once
TaggedRetryMetrics
.ofRetryRegistry(registry)
.bindTo(prometheusMeterRegistry);
return registry;
}
}
|
2) Autowired RetryRegistry实现
@Autowired
private RetryRegistry retryRegistry;
Retry retry = registry.retry("test");
Integer result = retry.executeSupplier(() -> {
System.out.println("====>1");
int a = 1 / 0;
return 0;
});
|
3) 配置grafana监控面板,import如下json配置:
展开源码
指标说明:
successful_without_retry:未经过重试的成功调用数量
successful_with_retry:经过重试的成功调用数量
failed_without_retry:未经过重试的失败调用数量
failed_with_retry:经过重试的失败调用数量
参考资料:
resilience4j-retry:https://resilience4j.readme.io/docs/retry
Guava retry : https://github.com/rholder/guava-retrying
Spring Retry:https://github.com/spring-projects/spring-retry
Smart Retry : https://github.com/hadoop002/smart-retry
分布式场景下的重试方案(可参考):https://www.infoq.cn/article/5fBoevKaL0GVGvgeac4Z
你投入得越多,就能得到越多得价值