🍃【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 次停止
其主要接口及策略介绍
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 的补充。
优雅重试适用场景
功能逻辑中存在不稳定依赖场景,需要使用重试获取预期结果或者尝试重新执行逻辑不立即结束。比如远程接口访问,数据加载访问,数据上传校验等等。
对于异常场景存在需要重试场景,同时希望把正常逻辑和重试逻辑解耦。
对于需要基于数据媒介交互,希望通过重试轮询检测执行逻辑场景也可以考虑重试方案。
版权声明: 本文为 InfoQ 作者【浩宇天尚】的原创文章。
原文链接:【http://xie.infoq.cn/article/bddd4970a78bf8d5e1ae0e2b1】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论