Java 开发利器之重试器
在平时开发中经常会遇到需要调用接口和外部服务的场景,但是有些接口服务方不能立即返回数据,而是需要处理一段时间才能返回真实的业务数据,如果没有处理完则直接返回一个中间状态的结果。
业务场景:
代码中存在依赖不稳定的场景,需要使用重试获取预期结果或者尝试重新执行逻辑不立即结束,比如远程接口访问,数据加载访问,数据上传校验等
对于异常需要重试的场景,同时希望把正常逻辑和重试逻辑解耦
对方接口不支持异步回调
接口返回的结果类似于{"errorCode":"-1", "errorMsg":"处理中", "result":""}
这样的结果,然后调用方需要过段时间(一般间隔几秒种后,需要根据具体的业务确认)再次调用才能获取真实的结果。
传统的写法如下(伪代码):
对于这种场景大家或多或少都遇到过,但上面这样的写法有几个很明显的弊端:
调用方需要不仅需要考虑多次调用的次数,还要考虑每次调用的间隔时间,尽量在最少调用情况下获取到最终结果(多一次请求意味着多一次网络开销,不方便实时调整)
多次调用过程中偶尔有一次调用出现异常(接口报错,网络异常),如果没有异常处理就会影响剩下次数的调用,无法保证高可用
多线程情况下上面的代码会出现并发问题,因为第一次调用的结果不一定是最早返回的,有可能后面调用的先返回,导致结果不是预期的
性能问题,如果使用多线程要考虑线程创建销毁和切换问题
当然这些问题自己实现完全可以解决,但已经有现成的轮子我们可以直接拿来用
解决方案:
上述场景可以考虑使用 google 的guava-retry
工具,guava-retryer
的特点如下:
支持设置重试次数和间隔时间,支持多种复杂场景的重试策略,延迟策略
支持多个异常或者自定义实体对象的重试源,让重试功能有更多的灵活性
线程安全,基于线程池实现隔离,我们只需要关注我们的业务逻辑实现即可
内部使用线程池管理线程
基于命令模式使用链式调用,使用方便 pom 依赖:
git 地址:https://github.com/rholder/guava-retrying
下面的代码模拟接口调用,设置重试次数为 5 次,每次调用间隔为 2 秒,如果调用过程中出现异常或结果满足重试条件的则再次调用直到最大次数(抛出异常):
RetryUtil
是我对guava-retrying
的封装实现,下面的代码大家可以直接拿去使用,只需要按照业务改下重试条件和重试任务以及重试间隔和次数即可:
重试间隔时间和重试次数可以做成可配置的,方便后续根据日志记录观察调整
相关重试策略和 api 介绍:
AttemptTimeLimiter
:单次任务执行时间限制(如果单次任务执行超时,则终止执行当前任务)BlockStrategies
:任务阻塞策略,默认策略为:BlockStrategies.THREAD_SLEEP_STRATEGY,也就是调用 Thread.sleep ()StopStrategy
:停止重试策略,提供三种:StopAfterDelayStrategy
:设定一个最长允许的执行时间,比如设定最长执行 10s,无论任务执行次数,只要重试的时候超出了最长时间,则任务终止,并返回重试异常 RetryExceptionNeverStopStrategy
:不停止,用于需要一直轮询直到返回期望结果的情况StopAfterAttemptStrategy
:设定最大重试次数,如果超出最大重试次数则停止重试,并返回重试异常WaitStrategy
:等待时长策略(控制时间间隔),返回结果为下次执行时长:FixedWaitStrategy
:固定等待时长策略RandomWaitStrategy
:随机等待时长策略IncrementingWaitStrategy
:递增等待时长策略ExponentialWaitStrategy
:指数等待时长策略FibonacciWaitStrategy
:斐波那契等待时长策略ExceptionWaitStrategy
:异常时长等待策略CompositeWaitStrategy
:复合时长等待策略 除了 google 的 retry 外,spring 框架也提供了一个重试工具:spring-retry
,该工具把重试操作模板定制化。还有 RxJava 里有个 retry 的 api 也能实现类似的用法,感兴趣的同学可以研究下。
评论