程序调用第三方接口可能会出现网络抖动、超时等异常情况,这时我们通常会想到当是重试。我们首先模拟一段业务逻辑,然后开始我们重试代码当编写
/** * 这个是需要执行的业务逻辑 * 定义了一个随机数,当低于阈值的时候,抛出异常 * 调用方catch住异常后进行重试 */ private void doSomething() { log.info("开始业务逻辑..."); int random = RandomUtils.nextInt(100); log.info("随机数为:{}", random); if (random < 90) { log.info("随机数低于阈值,准备重试"); throw new ServiceException("业务异常"); } log.info("结束业务逻辑..."); } 复制代码
最普通的重试逻辑,就是在调用方捕获到异常后,再次调用业务逻辑方法(递归),直到成功。 该方案简单粗暴
public void normal(int count) { if (count < 5) { count += 1; try { doSomething(); } catch (Exception e) { //此处可以定时休眠一点时间再次重试 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e1) { e1.printStackTrace(); } //重试 normal(count); } } } 复制代码
Spring-retry是一个开源工具包,该工具把重试操作模板定制化,可以设置重试策略和回退策略。同时重试执行实例保证线程安全,下面给出对应的示例代码
/** * Spring提供的Retry机制 */ public void springRetry() { // 构建重试模板实例 RetryTemplate retryTemplate = new RetryTemplate(); // 设置重试策略,主要设置重试次数和需要捕获的异常 SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true)); // 设置重试回退操作策略,主要设置重试间隔时间 ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); //初始间隔 backOffPolicy.setInitialInterval(1000); //最大间隔 backOffPolicy.setMaxInterval(10 * 1000L); //递增倍数(即下次间隔是上次的多少倍) backOffPolicy.setMultiplier(2); retryTemplate.setRetryPolicy(policy); retryTemplate.setBackOffPolicy(backOffPolicy); // 通过RetryCallback 重试回调实例包装正常逻辑逻辑,第一次执行和重试执行执行的都是这段逻辑 final RetryCallback<Object, Exception> retryCallback = context -> { log.info("开始重试,重试次数为: {}", context.getRetryCount()); doSomething(); return null; }; // 通过RecoveryCallback 重试流程正常结束或者达到重试上限后的退出恢复操作实例 final RecoveryCallback<Object> recoveryCallback = context -> { log.info("执行重试结束依然失败后的代码"); return null; }; try { // 由retryTemplate 执行execute方法开始逻辑执行 retryTemplate.execute(retryCallback, recoveryCallback); } catch (Exception e) { e.printStackTrace(); } } 复制代码
在上面的代码基础上,Spring提供了重试操作的注解,下面给出对应的示例代码。使用该方式的时候需要注意以下两点:
(2) @Retryable只能出现在最外层的方法,同一个类中,当某个方法调用加类该注解的方法时,重试不生效
/** * Spring提供的Retry机制 */ @Retryable(value = ServiceException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2)) public void springRetryAnnotation() { doSomething(); } /** * 重试失败后调用的方法(注意,需跟重处理方法在同一个类中) */ @Recover public void recover(ServiceException e) { log.info("执行重试结束依然失败后的代码"); } 复制代码
SpringRetry版本只能对异常进行重试,对于自定义对数据结构不能支持,如果有这方面需求的化,可以考虑用GuavaRetry进行重试,具体示例代码如下(SpringTime默认不集成,使用时需要手动添加maven依赖)
/** * guava提供的retry机制 */ public void guavaRetry() { //设置重试5次,同样可以设置重试超时时间 StopStrategy stopStrategy = StopStrategies.stopAfterAttempt(5); //设置每次重试间隔 WaitStrategy waitStrategy = WaitStrategies.exponentialWait(2, 10, TimeUnit.SECONDS); Retryer<Void> retryer = RetryerBuilder.<Void>newBuilder().retryIfException().withStopStrategy(stopStrategy) .withWaitStrategy(waitStrategy).build(); try { retryer.call(() -> { doSomething(); return null; }); } catch (ExecutionException | RetryException e) { e.printStackTrace(); } } 复制代码