简介
对于“接口/方法 重试”,介绍一种“重试”机制的实现,即Guava_Retrying,相对于传统的Spring_Retrying或者动态代理实现的重试功能而言,Guava_Retrying机制使用起来将更加容易、灵活性更强!
对于“重试”,那可是有场景限制的,不是什么场景都适合重试,比如参数校验不合法、写操作等(因为要考虑到写是否幂等)都不适合重试。
而诸如“远程调用超时”、“网络突然中断”等业务场景则可以进行重试,在微服务 治理框架中,通常都有自己的重试与超时配置,比如Dubbo可以设置retries=1,timeout=500调用失败只重试1次,超过500ms调用仍未返回则调用失败
对于“外部 RPC 调用”,或者“数据入库”等操作,如果一次操作失败,则可以进行多次重试,从而提高调用成功的可能性。
guava-retrying
小案例
demo
jar
<!--guava-retrying-->
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
util
public class RetryUtil {
private static final Logger log= LoggerFactory.getLogger(RetryUtil.class);
private static Integer i=1;
public static Integer execute() throws Exception{
log.info("----重试时 变量i的叠加逻辑,i={}----",i);
return i++;
}
public static void main(String[] args) {
//TODO:定义任务实例
Callable<String> callable= () -> {
Integer res=execute();
//当重试达到3 + 1次之后 我们就不玩了
if (res>3){
return res.toString();
}
return null;
};
//TODO:定义重试器
Retryer<String> retryer=RetryerBuilder.<String>newBuilder()
//TODO:当返回结果为Null时 - 执行重试
.retryIfResult(Predicates.isNull())
//TODO:当执行核心业务逻辑抛出RuntimeException - 执行重试
.retryIfRuntimeException()
//TODO:还可以自定义抛出何种异常时 - 执行重试
.retryIfExceptionOfType(IOException.class)
.build();
try {
retryer.call(callable);
} catch (ExecutionException | RetryException e) {
e.printStackTrace();
}
}
}
“重试机制”功能实现的核心在于定义Retryer实例以及Callable任务运行实例 ,特别是Retryer实例,可以设置“什么时机重试”。
除此之外,对于 Retryer实例 我们还可以设置“重试的次数”、“重试的时间间隔”、“每次重试时,定义Listener监听一些操作逻辑”等等。
public static void main(String[] args) {
//TODO:定义任务实例
Callable<String> callable= () -> {
return null;
};
//TODO:每次重试时 监听器执行的逻辑
RetryListener retryListener=new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
Long curr=attempt.getAttemptNumber();
log.info("----每次重试时 监听器执行的逻辑,当前已经是第 {} 次重试了----",curr);
if (curr == 3){
log.error("--重试次数已到,是不是得该执行一些补偿逻辑,如发送短信、发送邮件...");
}
}
};
//TODO:定义重试器
Retryer<String> retryer=RetryerBuilder.<String>newBuilder()
//TODO:当返回结果为Null时 - 执行重试
.retryIfResult(Predicates.isNull())
//TODO:当执行核心业务逻辑抛出RuntimeException - 执行重试
.retryIfRuntimeException()
//TODO:还可以自定义抛出何种异常时 - 执行重试
.retryIfExceptionOfType(IOException.class)
//TODO:每次重试时的时间间隔为5s
.withWaitStrategy(WaitStrategies.fixedWait(5L, TimeUnit.SECONDS))
//TODO:重试次数为3次,3次之后就不重试了
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
//TODO:每次重试时定义一个监听器listener,监听器的逻辑可以是 "日志记录"、"做一些补偿操作"...
.withRetryListener(retryListener)
.build();
try {
retryer.call(callable);
} catch (ExecutionException | RetryException e) {
e.printStackTrace();
}
}
业务场景
调用某个接口的方法,用于获取SysConfig配置表中某个字典配置记录,如果该字典配置记录不存在(即返回Null),那我们就重试3次,如果期间获取到了,那么就返回结果;3次过后,依旧为Null时,则执行一些补偿性的措施:即发送邮件通知给到指定的人员,让他们上去检查检查相应的数据状况!
@Service
public class RetryService {
private static final Logger log= LoggerFactory.getLogger(RetryService.class);
@Autowired
private SysConfigMapper sysConfigMapper;
@Autowired
private EmailSendService emailSendService;
//TODO:获取某个字典配置详情
public SysConfig getConfigInfo(final Integer id){
SysConfig config=sysConfigMapper.selectByPrimaryKey(id);
if (config==null){
//TODO:当没有查询到该数据记录时,执行重试逻辑
doRetry(id);
config=sysConfigMapper.selectByPrimaryKey(id);
}
return config;
}
//TODO:执行重试逻辑
private void doRetry(final Integer id){
//TODO:定义任务实例
Callable<SysConfig> callable= () -> {
return sysConfigMapper.selectByPrimaryKey(id);
};
//TODO:每次重试时 监听器执行的逻辑
RetryListener retryListener=new RetryListener() {
@Override
public <V> void onRetry(Attempt<V> attempt) {
Long curr=attempt.getAttemptNumber();
log.info("----每次重试时 监听器执行的逻辑,当前已经是第 {} 次重试了----",curr);
//当达到3次时 就执行一些补偿性的措施,如发送邮件通知某些大佬….
if (curr == 3){
log.error("--重试次数已到,是不是得该执行一些补偿逻辑,如发送短信、发送邮件...");
emailSendService.sendSimpleEmail("重试次数已到","请各位大佬上去检查一下sysConfig是否存在","1948831260@qq.com");
}
}
};
//TODO:定义重试器
Retryer<SysConfig> retryer= RetryerBuilder.<SysConfig>newBuilder()
//TODO:当返回结果为 false 时 - 执行重试(即sysCofig为null)
.retryIfResult(Objects::isNull)
//TODO:当执行核心业务逻辑抛出RuntimeException - 执行重试
.retryIfRuntimeException()
//TODO:还可以自定义抛出何种异常时 - 执行重试
.retryIfExceptionOfType(IOException.class)
//TODO:每次重试时的时间间隔为10s (当然啦,实际项目中一般是不超过1s的,如500ms,这里是为了方便模拟演示)
.withWaitStrategy(WaitStrategies.fixedWait(10L, TimeUnit.SECONDS))
//TODO:重试次数为3次,3次之后就不重试了
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
//TODO:每次重试时定义一个监听器listener,监听器的逻辑可以是 "日志记录"、"做一些补偿操作"...
.withRetryListener(retryListener)
.build();
try {
retryer.call(callable);
} catch (ExecutionException | RetryException e) {
e.printStackTrace();
}
}
}
test
@Autowired
private RetryService retryService;
@Test
public void method8() throws Exception{
final Integer id=11;
SysConfig entity=retryService.getConfigInfo(id);
log.info("---结果:{}",entity);
}