为什么要用重试机制
在如今的系统开发中,为了保证接口调用的稳定性和数据的一致性常常会引入许多第三方的库。就拿缓存和数据库一致性这个问题来说,就有很多的实现方案,如先更新数据库再删除缓存、先更新缓存再更新数据库,又或者是异步缓存写入。然而某些场景下出现更新数据库成功,但删除缓存失败,又或者是更新缓存失败但更新数据库失败了。因此为保证缓存数据库的一致性,我们可以尝试引入重试机制来实现,当数据库更新成功后去删除缓存,如果删除失败就进行重试。
引入 Guava Retry 库实现重试机制
引入依赖
在引入依赖前,先创建一个 Spring Boot 工程,这里我就不演示了。
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>1.0.6</version>
</dependency>
编写重试器的配置类
常见的重试策略
- 重试间隔时间,等待进入下一次重试的时间
- 重试的次数,超过设定次数就停止重试
- 重试的时间限制,规定重试时的时间限制,若重试过程中超过了这个时间就会抛出异常
- 重试时机,表明在什么场景需要重试,是抛出异常、还是返回 true,还是返回 false 呢。
在 config 包下编写一个重试器的配置类,设置重试的策略,当我们想在项目中使用时直接注入其依赖即可。
@Configuration
public class RetryConfig {
@Bean
public Retryer<Boolean> retryer() {
return RetryerBuilder.<Boolean>newBuilder()
.retryIfExceptionOfType(Exception.class) // 设置出现 Exception 异常就重试
.retryIfResult(Predicates.equalTo(false)) // 设置结果为 false 才重试
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS)) // 设置每次重试间隔为 2s
.withStopStrategy(StopStrategies.stopAfterAttempt(2)) // 设置重试次数为 2 次,超过 2 次就停止
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(3, TimeUnit.SECONDS)) // 设置每次重试的时间限制为 3s
.build();
}
}
直接引入 Bean 进行重试
创建一个 SpringBoot 的测试类,再注入入 Retryer 的 Bean。我写了一个方法来判断生成的随机数是否大于 10,大于就返回 true,否则返回 false,同时还打印重试的次数,如果第一次调这个函数就返回 true 的话就只重试了一次,因为每调用一次算一次重试,即 i == 1。
@SpringBootTest
public class RetryerTest {
@Resource
private Retryer<Boolean> retryer;
private int i = 1;
@Test
public void test() {
try {
retryer.call(() -> isGreaterThan10());
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (RetryException e) { // 超过指定的重试次数、超过的最大等待时间或超过重试时间就会抛这个异常
throw new RuntimeException(e);
}
}
// 判断生成的随机数是否大于 10
private boolean isGreaterThan10() {
Random random = new Random();
System.out.println("重试次数:" + i++);
int num = random.nextInt();
System.out.println(num);
return num > 10;
}
}
从测试结果就可以看出来,重试次数为 1,因为第一次调用就返回了 true。
再改造一下,看看它超过了规定的重试次数(这里我规定的是两次)会发生什么?
public class RetryerTest {
@Resource
private Retryer<Boolean> retryer;
private int i = 1;
@Test
public void test() {
try {
retryer.call(() -> isGreaterThan1000000000());
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (RetryException e) { // 超过指定的重试次数、超过的最大等待时间或超过重试时间就会抛这个异常
throw new RuntimeException(e);
}
}
// 判断生成的随机数是否大于 1000000000
private boolean isGreaterThan1000000000() {
Random random = new Random();
System.out.println("重试次数:" + i++);
int num = random.nextInt();
System.out.println(num);
return num > 1000000000;
}
}
从结果我们看出,调用两次后就抛出异常了,这个异常就是 RetryException,当超过指定的重试次数、超过的最大等待时间或超过重试时间就会抛这个异常。
当然这个方法写的不严谨,因为生成的数是随机的,如果两次生成的数都大于 1000000000,就不会抛出异常,要是第一次生成的数就大于 1000000000,那就只重试了一次。当然意思到了就行,具体的话就看你业务是啥样了,然后你再去调整就好了。
开源项目
前一阵子,我开源了一个既好玩又能学到东西的项目,有兴趣的同学可以移步学习和体验哈——我终于有我的开源项目了!!!-CSDN博客。