为什么需要重试
在软件开发过程中,有时会遇到方法调用异常的偶发性问题,绝大多数是因为网络不稳定导致的,这种情况需要进行重试
重试可以使处理更加健壮,减少失败的可能性
重试的一般思路
// 最大重试次数
int maxAttempts = 4;
for (int i = 0; i < maxAttempts; i++) {
try {
// 业务代码
// 成功执行完毕 break
} catch (Exception e) {
// 判断是否需要重试
// 是否需要休眠
}
}
// 判断最终是否成功,进行通知、告警等其他兜底方案的处理
如何优雅的进行重试
重试是一种通用的代码逻辑,可以使用模板模式进行封装
目前已有可靠的重试工具包以供使用,如 Spring Retry 和 Guava Retry
Spring Retry 原是 Spring Batch 批处理组件的重试工具部分,后独立为 Spring 的重试工具包
Spring Retry 提供了声明式的注解,方便在 Spring AOP 中使用,也提供了 RetryTemplate 编程式的调用方式
Spring Retry 声明式
@Retryable 注解
value 指定需要重试的异常类型
maxAttempts 指定最大重试次数
backoff 定义重试时的延迟规则
@Backoff 注解
delay 重试时间间隔
multiplier 每次重试的时间间隔要乘以该数
使用示例
/**
* 声明式
* 遇到 IllegalArgumentException 时重试
* 重试 4 次
* 初次重试间隔 1 秒,后面每次乘以 2
*/
@Retryable(value = IllegalArgumentException.class, maxAttempts = 4,
backoff = @Backoff(delay = 1000L, multiplier = 2))
public long testRetryableAnnotation() {
long round = Math.round(Math.random() * 5);
if (round <= 1) {
throw new UnsupportedOperationException("随机数小于等于 1");
}
if (round != 4) {
throw new IllegalArgumentException("随机数不等于 4");
}
return round;
}
Spring Retry 编程式
RetryTemplate
Spring Retry 提供的重试模板,需要指定 RetryPolicy 和 BackOffPolicy
RetryPolicy 重试策略
SimpleRetryPolicy 最大次数策略
ExceptionClassifierRetryPolicy 不同异常设置不同策略
BackOffPolicy 延迟策略
FixedBackOffPolicy 固定时间重试
ExponentialBackOffPolicy 指数退避策略
使用示例
public long testRetryTemplate() throws Throwable {
RetryTemplate retryTemplate = new RetryTemplate();
// 重试规则
// 针对不同异常定制重试策略
ExceptionClassifierRetryPolicy exceptionRetryPolicy = new ExceptionClassifierRetryPolicy();
HashMap<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<>();
// 定制 IllegalArgumentException 的策略
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(4);
policyMap.put(IllegalArgumentException.class, simpleRetryPolicy);
exceptionRetryPolicy.setPolicyMap(policyMap);
retryTemplate.setRetryPolicy(exceptionRetryPolicy);
// 延迟规则
// 初次重试间隔 1 秒,后面每次乘以 2
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000L);
backOffPolicy.setMultiplier(2);
retryTemplate.setBackOffPolicy(backOffPolicy);
Long res = retryTemplate.execute((RetryCallback<Long, Throwable>) context -> {
long round = Math.round(Math.random() * 5);
if (round <= 1) {
throw new UnsupportedOperationException("随机数小于等于 1");
}
if (round != 4) {
throw new IllegalArgumentException("随机数不等于 4");
}
return round;
});
return res;
}
Spring Retry 重试失败补偿
Spring Retry 提供了 @Recover 方法,用于在重试逻辑抛出异常后进行补偿
在旧版本中需要建一个有两个方法的类,一个方法使用 @Retryable 注解,另一个方法使用 @Recover 注解
在 1.3.0 版中 @Retryable 增加了 recover 属性,可以直接指定 @Recover 标注的方法,该方法的第一个参数需要是 Throwable 或其子类
@Retryable(value = IllegalArgumentException.class, maxAttempts = 4,
backoff = @Backoff(delay = 1000L, multiplier = 2), recover = "testRecover")
public long testRetryableAnnotation(String str) {
//
}
@Recover
public long testRecover(Throwable t, String str) {
log.error("recover", t);
return 9999;
}
Guava Retry 是基于 Guava 核心库实现的重试工具包
引入 Guava Retry
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
使用 Guava Retry
使用 RetryerBuilder 创建 Retryer 重试器
retryIfExceptionOfType 可以定义重试的异常种类
withWaitStrategy 定义等待策略
withStopStrategy 定义停止策略
需要重试的业务逻辑使用 Callable 包装,使用 Retryer call 执行
public long testGuavaRetry() throws Throwable {
// 定义重试器
Retryer<Long> retryer = RetryerBuilder.<Long>newBuilder()
.retryIfExceptionOfType(IllegalArgumentException.class)
.withWaitStrategy(WaitStrategies.fixedWait( 1L, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(4))
.build();
// 定义需要重试的逻辑
Callable<Long> callable = () -> {
long round = Math.round(Math.random() * 5);
if (round <= 1) {
throw new UnsupportedOperationException("随机数小于等于 1");
}
if (round != 4) {
throw new IllegalArgumentException("随机数不等于 4");
}
return round;
};
// 利用重试器调用请求
return retryer.call(callable);
}