重试的意义
-
为什么需要重试
在软件开发过程中,有时会遇到方法调用异常的偶发性问题,绝大多数是因为网络不稳定导致的,这种情况需要进行重试
重试可以使处理更加健壮,减少失败的可能性
-
重试的一般思路
// 最大重试次数 int maxAttempts = 4; for (int i = 0; i < maxAttempts; i++) { try { // 业务代码 // 成功执行完毕 break } catch (Exception e) { // 判断是否需要重试 // 是否需要休眠 } } // 判断最终是否成功,进行通知、告警等其他兜底方案的处理 -
如何优雅的进行重试
重试是一种通用的代码逻辑,可以使用模板模式进行封装
目前已有可靠的重试工具包以供使用,如 Spring Retry 和 Guava Retry
Spring 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 和 BackOffPolicyRetryPolicy 重试策略
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 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); }