重试场景问题
一般使用在对下层服务强依赖的场景。利用重试来解决网络异常带来的请求失败的情况,超时次数不应该太多,超时时间也比较关键。
通过设置请求时间和记录请求次数来判断是否需要重试即可。
重试机制的开源实现有guava-retry、spring-retry等。
场景
服务中需要调用一个上传服务,上传服务成功会返回一个链接。
原生写法
public void updateData0(Map<String, Object> dataMap) throws InterruptedException {
try {
if (!uploadData2FS(dataMap)) {
Thread.sleep(1000);
uploadData2FS(dataMap); //一次重试
}
} catch (Exception e) {
Thread.sleep(1000);
uploadData2FS(dataMap);//一次重试
}
}
public void updateData1(Map<String, Object> dataMap) throws InterruptedException {
for (int i = 0; i < 3; i++) {
try {
if (uploadData2FS(dataMap)) {
break;
} else {
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void updateData2(Map<String, Object> dataMap) throws InterruptedException {
try {
if (!uploadData2FS(dataMap)) {
retryUploadData2FS(dataMap, 1000L, 10);//延迟多次重试
}
} catch (Exception e) {
retryUploadData2FS(dataMap, 1000L, 10);//延迟多次重试
}
}
主要问题,业务逻辑与重试逻辑耦合在一起。结构不清晰,不利于维护。
Spring-Retry
maven引入
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
Demo :@Retryable
@Retryable(value = {RuntimeException.class}, maxAttempts = 5, backoff = @Backoff(delay = 5000L))
public void upload0(Map<String, Object> dataMap) {
RetrySimpleClient.uploadData2FS(dataMap);
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
String interceptor() default "";
Class<? extends Throwable>[] value() default {}; // 针对Throwable场景
Class<? extends Throwable>[] include() default {};
Class<? extends Throwable>[] exclude() default {};
String label() default "";
boolean stateful() default false;
int maxAttempts() default 3; // 重试次数
String maxAttemptsExpression() default "";
Backoff backoff() default @Backoff;
String exceptionExpression() default "";
String[] listeners() default {};
}
Demo:RetryTemplate
public void upload1(Map<String, Object> map) {
// 构建重试模板实例
RetryTemplate retryTemplate = new RetryTemplate();
// 设置重试策略,主要设置重试次数
SimpleRetryPolicy policy = new SimpleRetryPolicy(3, Collections.singletonMap(Exception.class, true));
// 设置重试回退操作策略,主要设置重试间隔时间
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(100);
retryTemplate.setRetryPolicy(policy);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
// 通过RetryCallback 重试回调实例包装正常逻辑逻辑,第一次执行和重试执行执行的都是这段逻辑
final RetryCallback<Object, Exception> retryCallback =
context -> RetrySimpleClient.uploadData2FSThrowable(map);
try {
retryTemplate.execute(retryCallback);
} catch (Exception e) {
e.printStackTrace();
}
}
Guava Retry
Guava retryer工具与spring-retry类似,都是通过定义重试者角色来包装正常逻辑重试,但是Guava retryer有更优的策略定义,在支持重试次数和重试频度控制基础上,能够兼容支持多个异常或者自定义实体对象的重试源定义,让重试功能有更多的灵活性。
Guava Retryer也是线程安全的,入口调用逻辑采用的是 java.util.concurrent.Callable 的 call() 方法
Guava Retry模块提供了一种通用方法, 可以使用Guava谓词匹配增强的特定停止、重试和异常处理功能来重试任意Java代码。
https://github.com/rholder/guava-retrying
maven 引入
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
Demo
public static void main(String[] args) {
//定义重试机制
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
// 任何异常
.retryIfException()
.retryIfRuntimeException()
// 指定异常类型
.retryIfExceptionOfType(Exception.class)
.retryIfException(Predicates.equalTo(new Exception()))
// 指定返回结果
.retryIfResult(Predicates.equalTo(false))
//等待策略,设置重试间隔
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
//停止策略,设置尝试次数
.withStopStrategy(StopStrategies.stopAfterAttempt(6))
//时间限制
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS))
.build();
Map<String, Object> dataMap = new HashMap<>();
try {
retryer.call(
() -> RetrySimpleClient.uploadData2FS(dataMap)
);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (RetryException e) {
e.printStackTrace();
}
}