本文最后更新于:2023-07-01T18:04:08+08:00
Guava-retrying
简介
在实际的过程中,我们开发一个函数功能往往会需要用到其他的服务,但是我们无法保证其他服务是稳定可用的,于是重试逻辑就是一个非常常见的行为,同时也是一个比较普遍适用的功能。同时考虑到对代码的侵入性,我们希望能够有一个统一的外部逻辑来进行函数的重试。guava-retrying
就能够提供这样的功能,它是google
Guava库的一个小扩展,允许为任意函数调用创建可配置的重试策略,为我们提供了一个简单易用的重试框架。该项目的官方地址为rholder/guava-retrying。
Guava:Google
Guava是一组由Google开源的Java通用库,其中提供了许多在Java中实用的功能。
Quick Start
首先我们需要引入相关依赖:
1 2 3 4 5
| <dependency> <groupId>com.github.rholder</groupId> <artifactId>guava-retrying</artifactId> <version>2.0.0</version> </dependency>
|
通常来说,我们需要重试的情况有函数出现异常,函数返回值与预期不一致等,因此我们首先可以通过随机数来模拟可能的情况,下面两个函数在运行的时候会随机返回,一个随机返回null,另一个随机抛出错误。这里MyException
是继承了RuntimeException
的自定义异常,主要用于异常统一。
1 2 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的时候进行重试,同时重试策略为重试三次之后不再重试。
1 2 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
。
1 2 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
对象的过程是非常类似的,所以对于类似的重试逻辑,我们完全可以再进行一层封装,方便后续的调用:
1 2 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对象会给我们提供许多重试的相关信息:
1 2 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作为返回类型