Google Guava Retry 优雅的重试方案
前言
使用场景
当需要两个或者多个组件协同工作而又无法保证调用返回的顺序时,我们可能会得到一个意料之外结果,可是下一时刻重新调用方法这个结果可能就正确了。当在这个场景下我们希望第一次得到意外结果时再调用一次方法以得到期望的结果。
我们调用外部接口时候获取数据的时候,因为一些外在因素可能会出现网络抖动,连接超时等导致接口调用失败,这个时候我们应该具备一个重试的机制。
总的来说为了使业务程序更加健壮,后续的重试操作会帮助我们最终达到成功的结果。
什么场景不适合重试
当一个方法过程是会更新或者写数据的时候这个方法和包含这个方法的过程要在保证幂等性的基础上使用重试机制。
将重试机制应用于只读方法过程则很少会出现问题。
提示:以下是本篇文章正文内容,下面案例可供参考
了解幂等性
针对同一个方法用同样的参数进行一次请求和重复的多次请求对系统造成的影响和所产生的数据是一致的
在使用重试器的时候幂等性
是我们必须要考虑的一个问题!
一、Guava Retry是什么?
Guava Retrying是Google Guava库的一个小扩展,可以为任意函数调用创建可配置的重试策略,例如,与正常运行时间不稳定的远程服务进行通信的对象。
guava retry 模块提供了一种通用方法,用于重试具有特定停止,重试和异常处理功能的任意Java代码,而Guava的谓词匹配增强了该功能。
与Spring retry比较
Spring retry 重试判定时针对异常类的,不能对返回数据做判断。
Spring retry 的可配置参数也比Guava Retry 少。
Guava Retry 相比较 Spring retry 功能更丰富,使用起来也更灵活,推荐使用。
二、使用步骤
1.引入库
代码如下(示例):
gradle
implementation group: 'com.github.rholder', name: 'guava-retrying', version: '2.0.0'
maven
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
2.码代码
import com.github.rholder.retry.*;
import com.google.common.base.Predicates;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.TimeUnit;
@RunWith(SpringRunner.class)
@SpringBootTest
(classes = TestClassSecond.class)
public class TestClassSecond {
public static final Logger log = LoggerFactory.getLogger(TestClassSecond.class);
/**
* 测试过程
*/
@Test
public void testProc() {
// 定义重试器并配置
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() // 模板类型为 要重试函数的返回类型
.retryIfExceptionOfType(Exception.class) // 重试条件 根据指定异常类型重试
.retryIfResult(Predicates.equalTo(false)) // 重试条件 根据返回值判断重试 异常类型判断 和 返回值判断 可以2选1 或者 两个条件同时设定
.withWaitStrategy(WaitStrategies.fixedWait(5, TimeUnit.SECONDS))
// .withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.MINUTES)) // 重试频率 每次叠加5分钟
.withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 最多重试3次
// .withStopStrategy(StopStrategies.neverStop()) // 判断重试条件 一直重试下去
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS)) // 每次 重试条件为真 重试频率固定2秒后重试
.build();
// 定义目标对象
TestClassSecond testClassSecond = new TestClassSecond();
// 定义参数
// String params = "https://blog.csdn.net/wangxudongx";
String params = "https://www.baidu.com";
try {
// 重试器调用
retryer.call(()-> testClassSecond.func(params));
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 重试的目标函数
* @param url
* @return
*/
private Boolean func(String url) {
RestTemplate restTemplate = new RestTemplate();
log.info("#INFO 事件调用 begin");
String result = restTemplate.getForObject(url, String.class);
log.info(""+result);
log.info("#INFO 事件调用 end");
if (result.length() > 10000) {
return false;
}
return true;
}
}
可以在判断重试条件里写Lambda表达式
// 定义重试器并配置
Retryer<BusinessDTO> retryer = RetryerBuilder.<BusinessDTO>newBuilder() // 模板类型为 要重试函数的返回类型
// .retryIfExceptionOfType(Exception.class) // 重试条件 根据指定异常类型重试
.retryIfResult(e->{ return e.getCode() == 1 ? true : false;}) // 重试条件 根据返回值判断重试 异常类型判断 和 返回值判断 可以2选1 或者 两个条件同时设定 ,还可使用Lambda
.withWaitStrategy(WaitStrategies.fixedWait(5, TimeUnit.SECONDS))
// .withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.MINUTES)) // 重试频率 每次叠加5分钟
.withStopStrategy(StopStrategies.stopAfterAttempt(2)) // 最多重试3次
// .withStopStrategy(StopStrategies.neverStop()) // 判断重试条件 一直重试下去
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(750, TimeUnit.MILLISECONDS)) // 每次 重试条件为真 重试频率固定750毫秒秒后重试
.build();
需要拿到最终运行的返回时
// 重试器调用
BusinessDTO dto = retryer.call(()->this.isAboutMeOfDialog(dialogRecordId,baseUserInfo.getUserId()));
需要考虑的问题。
- 在什么条件下重试?
- 我们应该重试几次?
- 每次重试的间隔设置为多少合适?
- 如果所有重试机会都用完了还是不成功怎么办?
总结
在合适的场景下使用Guava retry可以帮忙我们快速实现重试机制。而善用重试机制可以方便解决一些业务场景问题。