一、引入
在平时的开发工作中,重试机制,是一个很重要的逻辑,比如调用其他服务时,如果出现超时,那么可以等100毫秒后再进行调用,或者出现异常时,需要重试;可以重试多次,也可以重试1次,这个都是可以在程序中设定的。
实现上面的逻辑,最简单的方式就是使用for循环了,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package cn.ganlixin.guava; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class UseRetryer { private static final Logger log = LoggerFactory.getLogger(UseRetryer. class ); @Test public void testUseFor() throws InterruptedException { int retryTimes = 3 ; // 重试次数 // 使用for循环控制重试 for ( int i = 0 ; i < retryTimes; i++) { try { // 逻辑代码,比如调用其他服务的接口 } catch (Exception e) { log.warn( "第{}次运行出现异常," , i); // 如果出现异常,休眠100毫秒后重试(继续for循环) Thread.sleep( 100 ); continue ; } // 执行成功,立即终止for循环。 break ; } } } |
上面的代码,实现起来很简单,也很好理解,我之前也是这样做的。但是这样做,其实是有一个弊端的,因为业务逻辑代码,和一些控制操作(什么时候重试、隔多久重试)是写在一块的,比较乱。
本文所讲的Guava Retryer可以解决上面的问题(还有其他优点)。
二、快速上手
2.1、导入依赖
Guava Retryer并不属于Guava,他是一个基于Guava,提供重试机制的库。
guava retryer的github网址:GitHub - rholder/guava-retrying: This is a small extension to Google's Guava library to allow for the creation of configurable retrying strategies for an arbitrary function call, such as something that talks to a remote service with flaky uptime.,包含有使用文档。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version> 28.0 -jre</version> </dependency> <dependency> <groupId>com.github.rholder</groupId> <artifactId>guava-retrying</artifactId> <version> 2.0 . 0 </version> <!-- 排除与guava重复的依赖 --> <exclusions> <exclusion> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </exclusion> <exclusion> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> </exclusion> </exclusions> </dependency> |
2.2、第一个示例
先提及一下, retryer是一个重试器,可以对重试器进行设置,比如什么情况下重试、隔多久重试、重试多少次...
创建好重试器后,就可以使用重试器来进行执行指定的操作(实际的业务逻辑):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | package cn.ganlixin.guava; import com.github.rholder.retry.*; import org.junit.Test; import java.time.LocalTime; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class UseRetryer { // 需要重试执行的操作 private Boolean testCode() { System.out.println(LocalTime.now()); // 强制抛出异常,触发重试 if ( true ) { throw new RuntimeException( "手动测试抛出异常" ); } else { return false ; } } @Test public void testFirstRetryer() throws ExecutionException, RetryException, InterruptedException { // 创建一个重试器,重试器执行的方法,返回值为Boolean类型 Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() // 出现异常时,会重试 .retryIfException() // 失败后,隔2秒后重试 .withWaitStrategy(WaitStrategies.fixedWait( 2 , TimeUnit.SECONDS)) // 重试3次后,仍未成功,就不再重试 .withStopStrategy(StopStrategies.stopAfterAttempt( 3 )) .build(); // 使用重试器,执行具体逻辑 Boolean res = retryer.call(() -> { return testCode(); }); } } |
运行程序,输出:
1 2 3 4 5 6 7 8 | 23 : 35 : 37.753 23 : 35 : 39.754 23 : 35 : 41.759 com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 3 attempts. .......... Caused by: java.lang.RuntimeException: 手动测试抛出异常 .......... |
三、重试设置
上面简单介绍了怎么使用guava retryer,但是并没有看出retryer的优点,下面对guava retryer的重试设置进行介绍,比如,什么时候重试,重试多少次,每次充重试间隔多久..然后就可以发现guava retryer在进行重试时,挺“优雅”的。
3.1、重试条件设置
什么时候执行重试,Guava Retryer有多个匹配方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package cn.ganlixin.guava; import com.github.rholder.retry.RetryException; import com.github.rholder.retry.Retryer; import com.github.rholder.retry.RetryerBuilder; import com.github.rholder.retry.StopStrategies; import org.apache.commons.lang3.StringUtils; import org.junit.Test; import java.util.concurrent.ExecutionException; public class UseRetryer { @Test public void testWhenRetry() throws ExecutionException, RetryException { final Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder() // 当运行结果大于10的时候,需要重试(表达式返回true,则重试) .retryIfResult(res -> { return res > 10 ; }) // 当抛出异常时,就会进行重试 .retryIfException() // 或者当抛出ArithmeticException异常时,进行重试 .retryIfExceptionOfType(ArithmeticException. class ) // 如果抛出RuntimeException异常时,进行重试 .retryIfRuntimeException() // 捕获到异常,对异常进行处理,返回true时,会进行重试 .retryIfException(exception -> { System.out.println( "捕获到" + exception); // 可以对异常信息、异常种类进行处理,决定是否需要重试 return StringUtils.contains(exception.getMessage(), "wrong" ); }) // 重试3次 .withStopStrategy(StopStrategies.stopAfterAttempt( 3 )) .build(); Integer res = retryer.call(() -> { throw new Exception(); }); System.out.println(res); } } |
3.2、重试次数设置
前面说了什么情况下需要重试,那么需要重试多少次呢?
guava retryer中,如果没有设置重试多少次,那么将会没有休止地一直重试(死循环),所以建议一定要设置重试次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package cn.ganlixin.guava; import com.github.rholder.retry.Retryer; import com.github.rholder.retry.RetryerBuilder; import com.github.rholder.retry.StopStrategies; import org.junit.Test; public class UseRetryer { @Test public void testRetryStop() { Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder() // 抛出异常时重试 .retryIfException() // 设置什么时候停止重试 // 这里设置的是,执行3次都失败了就停止重试 .withStopStrategy(StopStrategies.stopAfterAttempt( 3 )) .build(); } } |
设置停止策略,主要是使用StopStrategies类的静态方法,如下:
1 2 3 4 5 6 7 8 | // 不停止,一直重试(默认) StopStrategies.neverStop() // attemptNumber次失败后,停止重试 StopStrategies.stopAfterAttempt( int attemptNumber) // 执行多久后停止(在未到停止的时间节点前,如果失败,会一致重试) StopStrategies.stopAfterDelay( 2 , TimeUnit.SECONDS) |
3.3、重试间隔设置
重试时间间隔设置,是指当执行失败后,执行下一次重试,需要等多久,这就是重试间隔策略。
下面是一个简单示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package cn.ganlixin.guava; import com.github.rholder.retry.Retryer; import com.github.rholder.retry.RetryerBuilder; import com.github.rholder.retry.WaitStrategies; import org.junit.Test; import java.util.concurrent.TimeUnit; public class UseRetryer { @Test public void testRetryInteval() { Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder() // 出现异常时重试 .retryIfException() // 设置重试时间间隔,此处设置固定时间间隔(2秒) .withWaitStrategy(WaitStrategies.fixedWait( 2 , TimeUnit.SECONDS)) .build(); } } |
和重试次数类似,重试间隔,主要使用WaitStrategies类的静态方法进行设置(有很多方法),常用的如下:
1 2 3 4 5 6 7 8 9 10 | // 失败后没有间隔,立即重试(默认) WaitStrategies.noWait() // 固定时间间隔(3秒) WaitStrategies.fixedWait( 3 , TimeUnit.SECONDS) // 设置第一次重试的时间间隔,然后后面每次重试时间间隔的增量 incrementingWait( long initialSleepTime, TimeUnit initialSleepTimeUnit, long increment, TimeUnit incrementTimeUnit) WaitStrategies.incrementingWait( 2 , TimeUnit.SECONDS, 1 , TimeUnit.SECONDS) // 解释:第一次重试间隔2秒,后面每次间隔时间是在前一个间隔上加1秒(就是3秒),再下一次是4秒 |
转自:学习使用Guava Retryer - 寻觅beyond - 博客园