在很多业务场景中,为了排除系统中的各种不稳定因素,以及逻辑上的错误,并最大概率保证获得预期的结果,重试机制都是必不可少的。
尤其是调用远程服务,在高并发场景下,很可能因为服务器响应延迟或者网络原因,造成我们得不到想要的结果,或者根本得不到响应。这个时候,一个优雅的重试调用机制,可以让我们更大概率保证得到预期的响应。
一个完备的重试实现,要很好地解决如下问题: 1. 什么条件下重试 2. 什么条件下停止 3. 如何停止重试 4. 停止重试等待多久 5. 如何等待 6. 请求时间限制 7. 如何结束 8. 如何监听整个重试过程
<dependency>
<groupId>com.github.rholdergroupId>
<artifactId>guava-retryingartifactId>
<version>2.0.0version>
dependency>
完整的参考实现
public Boolean test() throws Exception {
//定义重试机制
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
//retryIf 重试条件
.retryIfException()
.retryIfRuntimeException()
.retryIfExceptionOfType(Exception.class)
.retryIfException(Predicates.equalTo(new Exception()))
.retryIfResult(Predicates.equalTo(false))
//等待策略:每次请求间隔1s
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
//停止策略 : 尝试请求6次
.withStopStrategy(StopStrategies.stopAfterAttempt(6))
//时间限制 : 某次请求不得超过3s 否则重试
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(3, TimeUnit.SECONDS))
.build();
//定义请求实现
Callable<Boolean> callable = new Callable<Boolean>() {
int times = 1;
@Override
public Boolean call() throws Exception {
log.info("call times={}", times);
times++;
if (times == 2) {
throw new NullPointerException();
} else if (times == 3) {
throw new Exception();
} else if (times == 4) {
throw new RuntimeException();
} else if (times == 5) {
return false;
} else {
return true;
}
}
};
//利用重试器调用请求
return retryer.call(callable);
}
具体相关质料可自行百度,因为比较简单这里就不啰嗦了
1.POM依赖
<dependency>
<groupId>org.springframework.retrygroupId>
<artifactId>spring-retryartifactId>
dependency>
2.启用@Retryable
@EnableRetry
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
3.演示
@Service
public class TestRetryServiceImpl implements TestRetryService {
// 需要重试的方法 注意: 方法必须为 public
@Override
@Retryable(value = Exception.class,maxAttempts = 3,backoff = @Backoff(delay = 2000,multiplier = 1.5))
public int test(int code,String mess,String name) throws Exception{
System.out.println("test被调用,时间:"+ LocalTime.now());
if (code==400){
throw new Exception("情况不对头!");
}
System.out.println("test被调用,情况对头了!");
return 200;
}
// 回调方法 注意: 方法必须为 public
@Recover
public int recover(Exception e, int code,String name){
System.out.println("回调方法执行!!!!");
System.out.println("code: "+code);
System.out.println("name: "+name);
//记日志到数据库 或者调用其余的方法
return 500;
}
}
来简单解释一下 @Retryable注解中几个参数的含义:
- value:抛出指定异常才会重试
- include:和value一样,默认为空,当exclude也为空时,默认所有异常
- exclude:指定不处理的异常
- maxAttempts:最大重试次数,默认3次
- backoff:重试等待策略,默认使用@Backoff,@Backoff的value默认为1000L,我们设置为2000L;multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。
当重试耗尽时还是失败,会出现什么情况呢?
当重试耗尽时,RetryOperations可以将控制传递给另一个回调,即RecoveryCallback。Spring-Retry还提供了@Recover注解,用于@Retryable重试失败后处理方法。如果不需要回调方法,可以直接不写回调方法,那么实现的效果是,重试次数完了后,如果还是没成功没符合业务判断,就抛出异常。
对于@Recover注解的方法,需要特别注意的是:
- 方法的返回值必须与@Retryable方法一致
- 方法的第一个参数,必须是Throwable类型的,与@Retryable配置的异常一致,其他参数位,需要哪个参数,写进去就可以了只要原方法有- 就行, 该回调方法与重试方法写在同一个实现类里面