Guava-retrying重试机制

本文详细介绍了Guava库中Retrying模块的各种等待策略(WaitStrategy),包括无等待、固定间隔、随机间隔、增量间隔、斐波那契间隔及指数间隔等多种重试策略,并展示了如何根据异常类型定制不同的等待时间。

转载于:https://blog.youkuaiyun.com/aitangyong/article/details/53889036

guava-retrying提供了WaitStrategy接口,用来控制2次重试的时间间隔,这个接口与StopStrategy有的类似。内置的等待策略在WaitStrategies中定义。


  1. import java.io.IOException;
  2. import java.text.SimpleDateFormat;
  3. import java.util.Date;
  4. import java.util.concurrent.Callable;
  5. public class AlwaysExceptionTask implements Callable<Boolean> {
  6. private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss,SSS");
  7. private int times = 1;
  8. @Override
  9. public Boolean call() throws Exception {
  10. System.out.println(df.format(new Date()));
  11. int thisTimes = times;
  12. times++;
  13. if (thisTimes == 1) {
  14. throw new NullPointerException();
  15. } else if (thisTimes == 2) {
  16. throw new IOException();
  17. } else if (thisTimes == 3) {
  18. throw new ArithmeticException();
  19. } else {
  20. throw new Exception();
  21. }
  22. }
  23. }


WaitStrategies.noWait()失败后立刻重试,没有等待时间。

  1. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  2. .retryIfException()
  3. .withWaitStrategy(WaitStrategies.noWait())
  4. .build();
  5. System.out.println("begin..." + df.format(new Date()));
  6. try {
  7. retryer.call(new AlwaysExceptionTask());
  8. } catch (Exception e) {
  9. System.err.println("still failed after retry." + e.getCause().toString());
  10. }
  11. System.out.println("end..." + df.format(new Date()));


WaitStrategies.fixedWait(1, TimeUnit.SECONDS)间隔固定时间之后重试,比如每隔1s重试一次。

  1. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  2. .retryIfException()
  3. .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
  4. .build();
  5. System.out.println("begin..." + df.format(new Date()));
  6. try {
  7. retryer.call(new AlwaysExceptionTask());
  8. } catch (Exception e) {
  9. System.err.println("still failed after retry." + e.getCause().toString());
  10. }


WaitStrategies.randomWait(3, TimeUnit.SECONDS)间隔随机时间后重试,比如间隔0~3中随机时间后重试。

  1. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  2. .retryIfException()
  3. .withWaitStrategy(WaitStrategies.randomWait(3, TimeUnit.SECONDS))
  4. .build();
  5. System.out.println("begin..." + df.format(new Date()));
  6. try {
  7. retryer.call(new AlwaysExceptionTask());
  8. } catch (Exception e) {
  9. System.err.println("still failed after retry." + e.getCause().toString());
  10. }
  11. System.out.println("end..." + df.format(new Date()));


WaitStrategies.randomWait(2, TimeUnit.SECONDS, 5, TimeUnit.SECONDS)最小值,最大值之间的随机时间。

  1. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  2. .retryIfException()
  3. .withWaitStrategy(WaitStrategies.randomWait(2, TimeUnit.SECONDS, 5, TimeUnit.SECONDS))
  4. .build();
  5. System.out.println("begin..." + df.format(new Date()));
  6. try {
  7. retryer.call(new AlwaysExceptionTask());
  8. } catch (Exception e) {
  9. System.err.println("still failed after retry." + e.getCause().toString());
  10. }
  11. System.out.println("end..." + df.format(new Date()));




WaitStrategies.incrementingWait增量重试,重试的次数越多,等待时间间隔越长。incrementingWait需要传递2个参数,一个是initialSleepTime(第一次到第二次尝试的间隔),一个是increment(每增加一次尝试,需要增加的时间间隔)。第一次尝试是不需要等待的,因为guava-retrying中的第一次尝试,对应正常的第一次调用。从第二次重试开始,第n-1次到n次间隔是:initialSleepTime + (n-2)*increment。

1、WaitStrategies.incrementingWait(0, TimeUnit.SECONDS, 0, TimeUnit.SECONDS)等价于     WaitStrategies.noWait()。

2、 WaitStrategies.incrementingWait(1, TimeUnit.SECONDS, 0, TimeUnit.SECONDS)等价于WaitStrategies.fixedWait(1, TimeUnit.SECONDS)

3、 WaitStrategies.incrementingWait(0, TimeUnit.SECONDS, 1, TimeUnit.SECONDS)等价于WaitStrategies.fixedWait(1, TimeUnit.SECONDS)。


4、  WaitStrategies.incrementingWait(1, TimeUnit.SECONDS, 1, TimeUnit.SECONDS)



WaitStrategies.fibonacciWait()按照斐波那契数列等待。fibonacciWait(long multiplier,long maximumTime,TimeUnit maximumTimeUnit),multiplier单位固定是ms,maximumTime最大等待时间。n=1的时候,是无需等待的。当n>=2的时候,开始符合斐波那契数列。n=2的时候,等待1 * multiplier毫秒;n=3的时候,等待1 * multiplier毫秒;n=4的时候,等待2 * multiplier毫秒;n=5的时候,等待3 * multiplier毫秒;n=6的时候,等待5 * multiplier毫秒;n=7的时候,等待8 * multiplier毫秒;n=8的时候,等待15 * multiplier毫秒;......但是等待时间最长不超过maximumTime。

  1. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  2. .retryIfException()
  3. .withWaitStrategy(WaitStrategies.fibonacciWait(100, 10, TimeUnit.SECONDS))
  4. .build();
  5. System.out.println("begin..." + df.format(new Date()));
  6. try {
  7. retryer.call(new AlwaysExceptionTask());
  8. } catch (Exception e) {
  9. System.err.println("still failed after retry." + e.getCause().toString());
  10. }


WaitStrategies.exponentialWait按照指数递增(2的n次方)来等待,各个参数含义与fibonacciWait相同。

  1. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  2. .retryIfException()
  3. .withWaitStrategy(WaitStrategies.exponentialWait(100, 10, TimeUnit.SECONDS))
  4. .build();
  5. System.out.println("begin..." + df.format(new Date()));
  6. try {
  7. retryer.call(new AlwaysExceptionTask());
  8. } catch (Exception e) {
  9. System.err.println("still failed after retry." + e.getCause().toString());
  10. }


WaitStrategies.exceptionWait根据抛出的异常来决定等待的时间长短,没有什么实际用处,不过作为学习还是可以了解下,说不定将来能排上用场呢。下面我们定义1个任务,随机抛出不同异常。

  1. class RandomExceptionTask implements Callable<Boolean> {
  2. @Override
  3. public Boolean call() throws Exception {
  4. int round = (int) Math.floor(Math.random() * 8);
  5. System.out.println("round=" + round + ",time=" + df.format(new Date()));
  6. if (round == 1) {
  7. throw new NullPointerException();
  8. } else if (round == 2) {
  9. throw new IOException();
  10. } else if (round == 3) {
  11. throw new ArithmeticException();
  12. } else if (round == 4) {
  13. throw new IllegalStateException();
  14. } else if (round == 5) {
  15. throw new IndexOutOfBoundsException();
  16. } else {
  17. return true;
  18. }
  19. }
  20. }


如果我 们希望实现这种效果:如果出现了 NullPointerException,那么等待1s后再重试;如果抛出IOException,那等待2s后再重试;如果出现了ArithmeticException,那么等待3s后再重试;如果出现了IllegalStateException,那么等待4s后再重试;如果出现了IndexOutOfBoundsException,那么等待5s后再重试;否则不等待,立刻重试。
  1. // 根据不同异常,等待不同时间后重试
  2. private static <T extends Throwable> Function<T, Long> itsFunction(Class<T> exceptionClass) {
  3. Function<T, Long> result = new Function<T, Long>() {
  4. @Nullable
  5. @Override
  6. public Long apply(@Nullable T input) {
  7. if (input instanceof NullPointerException) {
  8. return 1 * 1000L;
  9. } else if (input instanceof IOException) {
  10. return 2 * 1000L;
  11. } else if (input instanceof ArithmeticException) {
  12. return 3 * 1000L;
  13. } else if (input instanceof IllegalStateException) {
  14. return 4 * 1000L;
  15. } else if (input instanceof IndexOutOfBoundsException) {
  16. return 5 * 1000L;
  17. } else {
  18. return 0L;
  19. }
  20. }
  21. };
  22. return result;
  23. }


下面是测试代码,可以看出不同的异常确实会导致不同的重试间隔。

  1. WaitStrategy exceptionJoin = WaitStrategies.join(
  2. WaitStrategies.exceptionWait(NullPointerException.class, itsFunction(NullPointerException.class)),
  3. WaitStrategies.exceptionWait(IOException.class, itsFunction(IOException.class)),
  4. WaitStrategies.exceptionWait(ArithmeticException.class, itsFunction(ArithmeticException.class)),
  5. WaitStrategies.exceptionWait(IllegalStateException.class, itsFunction(IllegalStateException.class)),
  6. WaitStrategies.exceptionWait(IndexOutOfBoundsException.class, itsFunction(IndexOutOfBoundsException.class))
  7. );
  8. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  9. .retryIfException()
  10. .withWaitStrategy(exceptionJoin)
  11. .build();
  12. System.out.println("begin..." + df.format(new Date()));
  13. try {
  14. retryer.call(new RandomExceptionTask());
  15. } catch (Exception e) {
  16. System.err.println("still failed after retry." + e.getCause().toString());
  17. }
  18. System.out.println("end..." + df.format(new Date()));



<think>我们正在参考用户提供的引用内容,特别是引用[1]和引用[2]关于guava-retrying库的信息。用户的需求是获取使用GuavaRetrying库实现Java重试机制的简单示例或教程。根据引用[1],我们知道guava-retrying是GoogleGuava库的一个扩展,用于为任意函数调用创建可配置的重试策略。引用[2]则提到了传统手写重试逻辑的缺点,并暗示了使用注解或库来更优雅地实现重试。我们将按照以下步骤提供帮助:1.引入依赖(因为用户可能需要知道如何开始)2.构建一个简单的重试器示例3.解释重试策略的常见配置选项由于引用中并没有直接给出代码示例,但根据引用[1]的项目地址,我们可以参考其文档。然而,用户要求我们使用站内引用,因此我们只能基于已有引用和我们的知识来编写。注意:引用[3]是关于Git工作区的,与当前问题无关,可以忽略。根据引用[2]中提到的传统重试逻辑的缺点,我们可以展示如何使用guava-retrying来替代手写逻辑。示例代码将包括:-定义重试条件(如何情况下重试-配置重试策略(重试次数、间隔等)-执行带有重试的操作下面开始逐步构建答案。</think> ### 使用 Guava Retrying 实现 Java 重试机制 Guava Retrying 是一个基于 Google Guava 的轻量级重试库,可优雅处理远程服务调用等不稳定操作。以下是完整实现示例: #### 1. 添加 Maven 依赖 ```xml <dependency> <groupId>com.github.rholder</groupId> <artifactId>guava-retrying</artifactId> <version>2.0.0</version> </dependency> ``` #### 2. 基础重试示例 ```java import com.github.rholder.retry.*; import java.util.concurrent.TimeUnit; public class RetryDemo { // 模拟不稳定的远程服务调用 public static boolean callRemoteService() throws Exception { double rand = Math.random(); if (rand > 0.3) throw new Exception("服务超时"); return true; } public static void main(String[] args) { // 配置重试策略 Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() .retryIfException() // 异常时重试 .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS)) // 每次间隔1秒 .withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 最多重试3次 .build(); try { retryer.call(() -> callRemoteService()); System.out.println("服务调用成功!"); } catch (Exception e) { System.err.println("所有重试失败: " + e.getMessage()); } } } ``` #### 3. 高级配置选项 | **配置项** | **说明** | **示例** | |-------------------|-------------------------------------------------------------------------|-----------------------------------------------------------------------| | 重试条件 | 根据异常类型或返回结果触发重试 | `.retryIfResult(res -> res == null)` | | 等待策略 | 控制重试间隔(固定/随机/指数) | `WaitStrategies.exponentialWait()` | | 停止策略 | 控制最大重试次数/时间 | `StopStrategies.stopAfterDelay(10, TimeUnit.SECONDS)` | | 监听器 | 重试事件监听 | `.withRetryListener(new RetryListener() {...})` | #### 4. 完整企业级示例 ```java Retryer<Response> retryer = RetryerBuilder.<Response>newBuilder() .retryIfExceptionOfType(ServiceTimeoutException.class) .retryIfResult(res -> !res.isSuccess()) .withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.SECONDS)) .withStopStrategy(StopStrategies.stopAfterDelay(30, TimeUnit.SECONDS)) .withRetryListener(new RetryListener() { @Override public <V> void onRetry(Attempt<V> attempt) { if (attempt.hasException()) { log.error("第{}次重试失败: {}", attempt.getAttemptNumber(), attempt.getExceptionCause().getMessage()); } } }) .build(); // 在业务代码中使用 Response result = retryer.call(() -> paymentService.confirm(orderId)); ``` #### 适用场景推荐: 1. 支付回调验证(避免因网络波动导致支付状态丢失) 2. 第三方 API 调用(地图服务/短信服务等不稳定接口)[^2] 3. 数据库连接恢复(瞬时连接失败自动重连) 4. 分布式服务调用(处理临时性节点故障) 相比于传统手动重试(如引用[2]所示),Guava Retrying 通过策略模式提供更强的灵活性和可观测性,同时避免代码重复。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值