前言
日常开发中 不仅仅在rpc调用的时候 需要重试啥的
有时候 接第三方系统 也得做一些重试的处理
之前一直都是自己利用function写了一个工具类
现在把自己写的基本思路 和 guava retry 和spring retry 一起记录一下 方便后续自己选择使用
https://github.com/rholder/guava-retrying https://github.com/spring-projects/spring-retry https://www.cnblogs.com/jelly12345/p/15292568.html
示例
利用function简略封装
- RetryUtils
package com.ming.example.retry;
import java.util.function.Supplier;
/**
* 重试工具类
*
* @author ming
* @date 2022-03-22 10:54:41
*/
public class RetryUtils {
/**
* 提供重试次数和间隔设置
*
* @param function 执行的函数 带返回值的
* @param count 重试次数 本身会执行一次也就是执行 count+1次还未成功 则抛出runTimeException
* @param interval 间隔时长 单位ms
* @author ming
* @date 2022-03-22 13:45:40
*/
public static <T> T retry(Supplier<T> function, int count, long interval) {
//当前执行次数
int executeNumber = 0;
while (executeNumber <= count) {
try {
return function.get();
} catch (Exception e) {
e.printStackTrace();
System.out.println("执行函数当前第" + executeNumber + "次!");
}
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
executeNumber++;
}
throw new RuntimeException("执行" + count + "次,无法成功调用!");
}
}
- 调用方式
public static int FLAG = 0;
/**
* 自己通过 function 去写一个公共处理重试的逻辑
* 写起来简单 但是很多地方 很僵硬 只适合对特定的一系列重试的处理
*
* @author ming
* @date 2022-03-22 13:54:16
*/
@Test
public void myFunction() {
System.out.println(RetryUtils.retry(() -> {
if (FLAG < 3) {
FLAG++;
throw new RuntimeException("测试异常,当前标记值为" + FLAG);
}
return FLAG;
}, 3, 1000L));
}
guava-retrying
https://github.com/rholder/guava-retrying
- pom配置
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
- 调用方式
public static int FLAG = 0;
/**
* guava retry
* 依旧的简单粗暴 和spring retry 主要的区别就是 增加了对于结果判断来重置重试 retryIfResult
*https://github.com/rholder/guava-retrying
* @author ming
* @date 2022-03-22 15:52:26
*/
@SneakyThrows
@Test
public void guava() {
Retryer<Integer> retry = RetryerBuilder.<Integer>newBuilder()
//配置重试监听器
// .withRetryListener()
//配置重试等待策略 默认是立即重试
// .withWaitStrategy()
//设置停止策略 默认不停止
// .withStopStrategy()
//配置阻塞策略 默认是sleep
// .withBlockStrategy()
//设置每一次重试的时间限制
// .withAttemptTimeLimiter()
//如果出现Exception 则重试
// .retryIfException()
//如果出现RuntimeException则重试
// .retryIfRuntimeException()
//如果出现指定的异常类型 则重试
// .retryIfExceptionOfType()
//如果返回结果满足表达式则重试
// .retryIfResult()
.retryIfResult(integer -> integer == null || integer <= 0)
.retryIfExceptionOfType(IOException.class)
.retryIfRuntimeException()
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
.build();
Integer result = retry.call(() -> {
if (FLAG < 2) {
FLAG++;
System.out.println("测试异常,当前标记值为" + FLAG);
throw new RuntimeException("测试异常,当前标记值为" + FLAG);
}
return FLAG;
});
System.out.println("执行结果:" + result);
}
spring-retry
https://github.com/spring-projects/spring-retry
- pom依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>${version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${version}</version>
</dependency>
- 调用方式
RetryTemplate方式调用
/**
* 使用 spring retry
* https://github.com/spring-projects/spring-retry
* https://www.cnblogs.com/jelly12345/p/15292568.html
* BackOff:补偿值,一般指失败后多久进行重试的延迟值。
* Sleeper:暂停应用的工具,通常用来应用补偿值。
* BackOffPolicy:补偿策略,决定失败后如何确定补偿值。
* RetryContext:重试上下文,代表了能被重试动作使用的资源。
* RetryPolicy:重试策略,决定失败能否重试。
* RecoveryCallback:定义一个动作recover,在重试耗尽后的动作。
* RetryCallback:具体的重试动作。
* RetryOperations:通过传递RetryCallback,进行重试操作。
* RetryState:重试状态,通常包含一个重试的键值。
* RetryStatistics和RetryListener,用来监控Retry的执行情况,并生成统计信息。
* <p>
* spring 可以注解 和 使用RetryTemplate 两种方式来
*
* <pre>
* {@code
*
* @Configuration
* @EnableRetry
* public class Application {
*
* @Bean
* public Service service() {
* return new Service();
* }
*
* }
*
* @Service
* class Service {
* @Retryable(RemoteAccessException.class)
* public void service() {
* // ... do something
* }
* @Recover
* public void recover(RemoteAccessException e) {
* // ... panic
* }
* }
* }
* </pre>
*
* @author ming
* @date 2022-03-22 13:55:04
*/
@SneakyThrows
@Test
public void spring() {
RetryTemplate retryTemplate = RetryTemplate.builder()
//超时时间 当前整个重试过程的超时时间
//.withinMillis()
//允许无限尝试
//.infiniteRetry()
//设置自定义的重试策略
//.customPolicy()
//设置自定义的退让策略
//.customBackoff()
//设置固定时间重试
.fixedBackoff(1000L)
//设置uniformRandomBackoff策略
//.uniformRandomBackoff()
//不暂停 立即重试
//.noBackoff()
//设置监听器
.withListener(new MethodInvocationRetryListenerSupport() {
@Override
protected <T, E extends Throwable> void doOnError(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
System.out.println("执行函数出现异常!" + throwable.getMessage());
super.doOnError(context, callback, throwable);
}
@Override
protected <T, E extends Throwable> void doClose(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
System.out.println("关闭重试!");
super.doClose(context, callback, throwable);
}
})
//.withListeners()
//重试上线 3 = 初始执行一次+两次重试
.maxAttempts(3)
//重试间隔策略 currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)
// .exponentialBackoff(100,2,10000)
//指定重试异常的类型
.retryOn(Exception.class)
//指定忽略重试异常的类型
//.notRetryOn()
//遍历异常处理 会尝试向上找继承关系
.traversingCauses()
.build();
int result = retryTemplate.execute((RetryCallback<Integer, Throwable>) context -> {
if (FLAG < 2) {
FLAG++;
System.out.println("测试异常,当前标记值为" + FLAG);
throw new RuntimeException("测试异常,当前标记值为" + FLAG);
}
return FLAG;
}, (RecoveryCallback<Integer>) context -> {
//当重试耗尽的时候 进入此函数
System.out.println("重试耗尽!!!!!!!");
return 0;
});
System.out.println(result);
}
注解调用方式
@Configuration
@EnableRetry
public class Application {
@Bean
public Service service() {
return new Service();
}
}
@Service
class Service {
@Retryable(RemoteAccessException.class)
public void service() {
// ... do something
}
@Recover
public void recover(RemoteAccessException e) {
// ... panic
}
}
总结
自己实现 可以按照对应的需求 实现的更加精细 示例只是个例子 需要自己去额外的处理其他的细节 guava-retrying和spring-retry 大多数使用方式一样 区别较大的地方 就是guava-retrying 支持根据结果去继续重试