0%

函数重试方案比较

前言

日常开发中 不仅仅在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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    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 + "次,无法成功调用!");
    }

    }
  • 调用方式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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配置

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.github.rholder</groupId>
    <artifactId>guava-retrying</artifactId>
    <version>2.0.0</version>
    </dependency>
  • 调用方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
        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依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <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方式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
    /**
* 使用 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);

}

注解调用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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 支持根据结果去继续重试