基于 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:经过重试的失败调用数量

参考资料:

posted @ 2021-02-28 13:33  N!CE波  阅读(677)  评论(0编辑  收藏  举报