写点什么

🍃【Spring 专题】「实战系列」重新回顾一下异常重试框架 Spring Retry 的功能指南

作者:浩宇天尚
  • 2021 年 11 月 19 日
  • 本文字数:3118 字

    阅读完需:约 10 分钟

🍃【Spring专题】「实战系列」重新回顾一下异常重试框架Spring Retry的功能指南

重试机制的业务背景

外部服务对于调用者来说一般都是不可靠的,尤其是在网络环境比较差的情况下,网络抖动很容易导致请求超时等异常情况,这时候就需要用失败重试策略重新调用 API 接口来获取。


在分布式系统中,为了保证数据分布式事务的强一致性,大家在调用 RPC 接口或者发送 MQ 时,针对可能会出现网络抖动请求超时情况采取一下重试操作。 大家用的最多的重试方式就是 MQ 了,但是如果你的项目中没有引入 MQ,那就不方便了。

重试策略的介绍和限制

重试策略在服务治理方面也有很广泛的使用,通过定时检测,来查看服务是否存活。


重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。


  • 远程调用超时、网络突然中断可以重试。在微服务治理框架中,通常都有自己的重试与超时配置,比如 dubbo 可以设置 retries=1,timeout=500 调用失败只重试 1 次,超过 500ms 调用仍未返回则调用失败。

重试的场景有哪些?

外部 RPC 调用,或者数据入库等操作,如果一次操作失败,可以进行多次重试,提高调用成功的可能性。

常用的重试框架

  • Spring 异常重试框架 Spring Retry:Spring Retry 支持集成到 Spring 或者 Spring Boot 项目中,而它支持 AOP 的切面注入写法,所以在引入时必须引入 aspectjweaver.jar 包。

  • sisyphus 综合了 spring-retry 和 gauva-retrying 的优势,使用起来也非常灵活。

  • https://github.com/houbb/sisyphus

  • guava-retrying 模块提供了一种通用方法, 可以使用 Guava 谓词匹配增强的特定停止、重试和异常处理功能来重试任意 Java 代码。

spring-retry 的重试机制

Spring Retry 为 Spring 应用程序提供了声明性重试支持。 它用于 Spring 批处理、Spring 集成、Apache Hadoop(等等)的 Spring。


This is a small extension to Google’s Guava library to allow for the creation of configurable retrying strategies for an arbitrary function call, such as something that talks to a remote service with flaky uptime.

Maven 配置

启用重试功能

启动类上面添加 @EnableRetry 注解,启用重试功能,或者在使用 retry 的 service 上面添加也可以,或者 Configuration 配置类上面。建议所有的 Enable 配置加在启动类上,可以清晰地统一管理使用的功能。


添加 @Retryable 和 @Recover 注解

@Retryable 注解,被注解的方法发生异常时会重试
  • value:指定发生的异常进行重试

  • include:和 value 一样,默认空,当 exclude 也为空时,所有异常都重试

  • exclude:指定异常不重试,默认空,当 include 也为空时,所有异常都重试

  • maxAttemps:重试次数,默认 3

  • backoff:重试补偿机制,默认没有

@Backoff 注解
  • delay:指定延迟后重试

  • multiplier:指定延迟的倍数,比如 delay=5000l,multiplier=2 时,第一次重试为 5 秒后,第二 次为 10 秒,第三次为 20 秒

@Recover 注解

当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调。


guava-retry

guava-retrying 模块提供了一种通用方法, 可以使用 Guava 谓词匹配增强的特定停止、重试和异常处理功能来重试任意 Java 代码。


guava-retry 的 Git 地址

https://github.com/rholder/guava-retrying

优势

guava retryer 工具与 spring-retry 类似,都是通过定义重试者角色来包装正常逻辑重试,但是 Guava retryer 有更优的策略定义,在支持重试次数和重试频度控制基础上,能够兼容支持多个异常或者自定义实体对象的重试源定义,让重试功能有更多的灵活性。


Guava Retryer 也是线程安全的,入口调用逻辑采用的是 java.util.concurrent.Callable 的 call() 方法,遇到异常之后,重试 3 次停止


public static void main(String[] args) {    Callable<Boolean> callable = new Callable<Boolean>() {        @Override        public Boolean call() throws Exception {            // do something useful here            LOGGER.info("call...");            throw new RuntimeException();        }    };
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() .retryIfResult(Predicates.isNull()) .retryIfExceptionOfType(IOException.class) .retryIfRuntimeException() .withStopStrategy(StopStrategies.stopAfterAttempt(3)) .build(); try { retryer.call(callable); } catch (RetryException | ExecutionException e) { e.printStackTrace(); }}
复制代码


其主要接口及策略介绍


  • Attempt:一次执行任务;

  • AttemptTimeLimiter:单次任务执行时间限制(如果单次任务执行超时,则终止执行当前任务);

  • BlockStrategies:任务阻塞策略(通俗的讲就是当前任务执行完,下次任务还没开始这段时间做什么……- - BlockStrategies.THREAD_SLEEP_STRATEGY 也就是调用 Thread.sleep(sleepTime);

  • RetryException:重试异常;

  • RetryListener:自定义重试监听器,可以用于异步记录错误日志;

  • StopStrategy:停止重试策略,提供三种:

  • StopAfterDelayStrategy:设定一个最长允许的执行时间;比如设定最长执行 10s,无论任务执行次数,只要重试的时候超出了最长时间,则任务终止,并返回重试异常 RetryException;

  • NeverStopStrategy:不停止,用于需要一直轮训直到返回期望结果的情况;

  • StopAfterAttemptStrategy:设定最大重试次数,如果超出最大重试次数则停止重试,并返回重试异常;

  • WaitStrategy:等待时长策略(控制时间间隔),返回结果为下次执行时长:

  • FixedWaitStrategy:固定等待时长策略;

  • RandomWaitStrategy:随机等待时长策略(可以提供一个最小和最大时长,等待时长为其区间随机值)

  • IncrementingWaitStrategy:递增等待时长策略(提供一个初始值和步长,等待时间随重试次数增加而增加)

  • ExponentialWaitStrategy:指数等待时长策略;

  • FibonacciWaitStrategy :Fibonacci 等待时长策略;

  • ExceptionWaitStrategy :异常时长等待策略;

  • CompositeWaitStrategy :复合时长等待策略;

根据结果判断是否重试

使用场景:如果返回值决定是否要重试。

重试接口


重试策略设定无限重试

使用场景:在有异常情况下,无限重试(默认执行策略),直到返回正常有效结果;


根据异常判断是否重试

使用场景:根据抛出异常类型判断是否执行重试。



等待策略——设定重试等待固定时长策略

使用场景:设定每次重试等待间隔固定为 10s;


等待策略——设定重试等待时长固定增长策略

场景:设定初始等待时长值,并设定固定增长步长,但不设定最大等待时长;


例如:调用间隔时间递增 1 秒:


重试框架的总结

优雅重试共性和原理

  • 正常和重试优雅解耦,重试断言条件实例或逻辑异常实例是两者沟通的媒介,还有一种方式,是开发者自己编写重试机制,但是大多不够优雅

  • 约定重试间隔,差异性重试策略,设置重试超时时间,进一步保证重试有效性以及重试流程稳定性。

  • 都使用了命令设计模式,通过委托重试对象完成相应的逻辑操作,同时内部封装实现重试逻辑。

  • spring-retry 和 guava-retry 工具都是线程安全的重试,能够支持并发业务场景的重试逻辑正确性。两种方式都是比较优雅的重试策略,Spring-retry 配置更简单,实现的功能也相对简单,Guava 本身就是谷歌推出的精品 java 类库,guava-retry 也是功能非常强大,相比较于 Spring-Retry 在是否重试的判断条件上有更多的选择性,可以作为 Spring-retry 的补充。

优雅重试适用场景

功能逻辑中存在不稳定依赖场景,需要使用重试获取预期结果或者尝试重新执行逻辑不立即结束。比如远程接口访问,数据加载访问,数据上传校验等等。


对于异常场景存在需要重试场景,同时希望把正常逻辑和重试逻辑解耦。


对于需要基于数据媒介交互,希望通过重试轮询检测执行逻辑场景也可以考虑重试方案。

发布于: 5 小时前阅读数: 5
用户头像

浩宇天尚

关注

🏆 InfoQ写作平台-签约作者 🏆 2020.03.25 加入

【个人简介】酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“ 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、APM专题及微服务/分布式体系等

评论

发布
暂无评论
🍃【Spring专题】「实战系列」重新回顾一下异常重试框架Spring Retry的功能指南