本文最后更新于:2023-07-01T18:04:08+08:00
                  
                  
                
              
            
            
              
                
                Guava-retrying
简介
在实际的过程中,我们开发一个函数功能往往会需要用到其他的服务,但是我们无法保证其他服务是稳定可用的,于是重试逻辑就是一个非常常见的行为,同时也是一个比较普遍适用的功能。同时考虑到对代码的侵入性,我们希望能够有一个统一的外部逻辑来进行函数的重试。guava-retrying就能够提供这样的功能,它是google
Guava库的一个小扩展,允许为任意函数调用创建可配置的重试策略,为我们提供了一个简单易用的重试框架。该项目的官方地址为rholder/guava-retrying。
Guava:Google
Guava是一组由Google开源的Java通用库,其中提供了许多在Java中实用的功能。
Quick Start
首先我们需要引入相关依赖:
| 12
 3
 4
 5
 
 | <dependency><groupId>com.github.rholder</groupId>
 <artifactId>guava-retrying</artifactId>
 <version>2.0.0</version>
 </dependency>
 
 | 
通常来说,我们需要重试的情况有函数出现异常,函数返回值与预期不一致等,因此我们首先可以通过随机数来模拟可能的情况,下面两个函数在运行的时候会随机返回,一个随机返回null,另一个随机抛出错误。这里MyException是继承了RuntimeException的自定义异常,主要用于异常统一。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | private Boolean randomFailed() {boolean res = random.nextBoolean();
 if (res) {
 System.out.println("return true");
 return true;
 } else {
 System.out.println("return null");
 return null;
 }
 }
 
 private void randomThrowException() {
 boolean res = random.nextBoolean();
 if (res) {
 System.out.println("throw my exception.");
 throw new MyException("res is true");
 } else {
 System.out.println("dont throw my exception");
 }
 }
 
 | 
上面的两个方法在运行的时候是带有随机性的,无法保证每次运行都能返回正确的结果,因此我们需要引入重试功能。guava-retrying采用Callable的方式对需要进行重试的方法进行包装,然后对运行结果进行监听,从而控制重试逻辑。
对于第一个随机失败的方法来说,我们可以设置当结果返回null的时候进行重试,那么可以用下面的代码完成。这里需要通过RetryerBuilder来构造出一个Retryer对象,通过该对象来调用Callable对象。在构造的时候,我们可以指定对应的重试逻辑,像这里我们就可以指定当结果返回null的时候进行重试,同时重试策略为重试三次之后不再重试。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | public Boolean randomFailedWithRetry() {Callable<Boolean> callable = new Callable<Boolean>() {
 @Override
 public Boolean call() throws Exception {
 return randomFailed();
 }
 };
 Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
 .retryIfResult(Objects::isNull)
 .withStopStrategy(StopStrategies.stopAfterAttempt(3))
 .build();
 
 try {
 return retryer.call(callable);
 } catch (ExecutionException e) {
 throw new RuntimeException(e);
 } catch (RetryException e) {
 throw new RuntimeException(e);
 }
 }
 
 | 
对于第二个随机抛出错误的方法来说,我们也可以设置当出现异常的时候进行重试,相关代码如下。注意这里设置返回类型为Void,还是需要一个返回结果的,这里选择返回为null。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | public void randomThrowExceptionWithRetry() {Callable<Void> callable = new Callable<Void>() {
 @Override
 public Void call() throws Exception {
 randomThrowException();
 return null;
 }
 };
 Retryer<Void> retryer = RetryerBuilder.<Void>newBuilder()
 .retryIfExceptionOfType(MyException.class)
 .withStopStrategy(StopStrategies.stopAfterAttempt(3))
 .build();
 
 try {
 retryer.call(callable);
 } catch (ExecutionException e) {
 throw new RuntimeException(e);
 } catch (RetryException e) {
 throw new RuntimeException(e);
 }
 }
 
 | 
我们可以看到上面,构造出Retryer对象的过程是非常类似的,所以对于类似的重试逻辑,我们完全可以再进行一层封装,方便后续的调用:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public static <T> T retry(Callable<T> callable, int retryTimes,Class<? extends Throwable> retryException) throws RuntimeException {
 Retryer<T> retryer = RetryerBuilder.<T>newBuilder()
 .retryIfExceptionOfType(retryException)
 .withStopStrategy(StopStrategies.stopAfterAttempt(retryTimes))
 .build();
 
 try {
 return retryer.call(callable);
 } catch (Exception e) {
 throw new RuntimeException(e);
 }
 }
 
 | 
当然除了代码中使用到的这些,guava-retrying还提供了很多其他更加灵活的重试策略,我们可以在使用的时候进行参考使用。
RetryListener
如果在发生重试之后,我们需要做一些额外的处理动作,例如告警,那么可以使用RetryListener。每次重试之后,guava-retrying会自动回调我们注册的监听。当然也可以注册多个
RetryListener,会按照注册顺序依次调用。
首先,我们可以实现自定义的监听类,需要实现RetryListener接口以及其中的方法。其中的Attempt对象会给我们提供许多重试的相关信息:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public class MyRetryListener implements RetryListener {@Override
 public <V> void onRetry(Attempt<V> attempt) {
 
 System.out.println("retry times = " + attempt.getAttemptNumber());
 
 System.out.println("delay = " + attempt.getDelaySinceFirstAttempt());
 
 System.out.println("hasException = " + attempt.hasException());
 System.out.println("hasResult = " + attempt.hasResult());
 
 if(attempt.hasException()){
 System.out.println("causeBy = " + attempt.getExceptionCause());
 }else{
 System.out.println("result = " + attempt.getResult());
 }
 }
 }
 
 | 
完成之后,只需要在构造Retry对象的时候增加相关配置即可:
| 1
 | .withRetryListener(new MyRetryListener())
 | 
参考文章
- Java开发利器之重试框架guava-retrying
- 重试工具: Guaa
Retryer
- 线程任务接口Callable使用void作为返回类型