微服务盛行的开发利器-重试组件库guava-retrying

945 阅读3分钟

前言

在目前微服务开发盛行的大潮下,我们经常会使用Sentinel、豪猪哥等服务熔断功能,来避免对一个服务的一次性打击,那么当一些重要的业务发生服务熔断时,我们不可能单纯的就取消了某个业务,而是希望它能够自动重试并且能够给我们打印出一些重试日志来。

我们可以通过简单的if-else语句 + for语句进行不断的重试,那么所有需要重试的语句都加上if、for语句正常人肯定都不喜欢这样的代码。而今天介绍的guava-retrying就是一个简单、优雅的重试组件库。

如何使用

  1. Maven依赖
<!-- guava-retrying -->
<dependency>
    <groupId>com.github.rholder</groupId>
    <artifactId>guava-retrying</artifactId>
    <version>2.0.0</version>
</dependency>

注意:当有其他依赖使用了低版本的guava的话会冲突报错,解决方法就是将低版本guava排除或者引入guava19.0版本即可。

使用这个组建库最核心的地方就是Retryer,而建造一个Retryer有三个维度需要我们掌握

何时重试(以retryIf开头)

在这个维度分两种情况1. 发生异常时重试 2. 返回结果满足断言时重试

public void guavaRetryTest01(){
    Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
        // 发生异常时重试
        .retryIfException() // 发生任何异常时重试
        .retryIfExceptionOfType(MySQLException.class) // 当发生异常isAssignedFrom 指定异常时重试
        // 当指定范型的返回结果满足predicate时重试
        .retryIfResult(result -> result % 2 == 0) 
        .build();
}

怎样重试(withXXXStrategy)

在retryer中策略分为三种类型:Stop、Wait、Block。分别对应着如何停下重试、等待多久、如何进行等待

每个类型对应着三个接口

@Test
public void guavaRetryTest02(){
    Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
        // 停止重试策略: stopAfterAttempt(int) 重试n次后不再重试 stopAfterDelay(long) 距离第一次执行超过n毫秒不再重试 neverStop() 用不停止
        .withStopStrategy(StopStrategies.stopAfterAttempt(3))
        // 重试等待间隔策略: 决定每次重试的间隔: 参考WaitStrategy的实现类 最简单的两种 Random 和fixed
        .withWaitStrategy(WaitStrategies.fixedWait(5, TimeUnit.SECONDS))
        // 阻塞策略: 发生重试时如何阻塞 只有一个实现类ThreadSleep
        .withBlockStrategy(BlockStrategies.threadSleepStrategy())
        .build();
}

重试时做什么(RetryListener)

当发生重试时,我们希望使用监听器记录日志或者其他额外操作时,我们可以通过实现一个Lisnter来满足这样的需求。

通过RetryLisner接口 + RetryerBuidler.withRetryListener方法来实现这样的效果

@Test
public void guavaRetryTest03(){
    Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
        .withRetryListener(new CustomRetryerListener())
        .build();
}

static class CustomRetryerListener implements RetryListener{
    Logger logger = LoggerFactory.getLogger(ReteyerBuidlerDemo.class);
    @Override
    public <V> void onRetry(Attempt<V> attempt) {
        logger.warn(getClass() +  "第" + attempt.getAttemptNumber() + "次重试");
    }
}

案例总结

综合上面的三个维度,我们就可以根据需要来进行重试了,比如我们要进行数据库的异步插入,一旦这个地方发生了个异常数据就丢失了,这时候我们可以通过重试框架进行。

  1. 我们希望数据库插入的时候返回值<1的时候,重隔3s进行重试,超过1分钟就不再重试了,并且打印一下日志
//mockdata
int retryCount = 0;
public Integer mapperInsert(){
    return retryCount;
}

public void insert() throws ExecutionException, RetryException{
    Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
        .retryIfResult(integer -> integer == 0)
        .withStopStrategy(StopStrategies.stopAfterDelay(30, TimeUnit.SECONDS))
        .withWaitStrategy(WaitStrategies.fixedWait(5, TimeUnit.SECONDS))
        .withRetryListener(new CustomRetryerListener())
        .build();
    retryer.call(() -> mapperInsert());
}
static class CustomRetryerListener implements RetryListener{
    Logger logger = LoggerFactory.getLogger(ReteyerBuidlerDemo.class);
    @Override
    public <V> void onRetry(Attempt<V> attempt) {
        logger.warn(getClass() +  "第" + attempt.getAttemptNumber() + "次重试");
    }
}

总结

以上就是这个guava-retrying组件库的使用方式了,通过建造者模式的retryer以达到消除if、for语句的效果。实现方式特别的简单。而且可以根据维度和策略来实现不同的效果。