在业务系统中,经常会遇到进行业务重试的场景,当一个执行方法失败时,可能需要进行几次重试,如果重试时成功了,仍然认为业务是正确的。否则则throw相应的异常。这种通用的重试场景则可以使用标准的第三方重试库来完成,以避免自己在业务代码中写类似while try 的代码。同时,因为重试的条件以及处理的逻辑均不太一样,通过一个标准的重试库来完成此操作,也是很有必要的。
本文描述了spring提供的重试库spring-retry,通过提取相应的概念模型,了解其工作原理以及执行机制.
版本:1.2.2.RELEASE
会使用到的注释
Retryable
用于标识一个可以进行重试的方法,即一个方法如果需要在throw异常之后进行重试,则可以使用此注解进行标记.同时,此注解上标识了可以进行重试的一些条件以及次数信息。
Backoff
此标识一个方法在进行下一次重试时,需要暂停的一些手法以及参数信息,比如sleep多少秒,或者是是在一个区间范围内sleep。
Recover
此标识当一个方法执行多次都失败之后,进行的一个failback处理。
主要的业务接口
RetryContext
重试上下文,即维护了整个重试周期中相应调用对象,调用次数,以及在调用过程中的异常对象等,其它接口均从此对象中获取相应的数据.
RetryOperations
封装了一个主要可重试操作,可以理解为进行主要逻辑操作的template对象。
RetryPolicy
与@Backoff相对应的一个接口,用于在整个重试逻辑中,处理何时该重试,以及控制相应的重试上下文信息。
RetryListener
监听器,用于在重试过程中,对开始,重试出错,结束时进行拦截操作。默认情况下,此类为null,即无作用。
RetryCallback
简单的重试回调对象,用于封装原来的可重试方法,将整个操作统一使用callback来表示,以隔离相应的实现。
RecoveryCallback
与@Recover相对应,即最终进行failback的回调对象.
整个spring-retry的执行逻辑均封装在RetryTemplate这个类中,然后在外部通过RetryConfiguration来配置一些额外的业务接口信息,最终形成一个bean拦截器,通过拦截bean的调用来完成重试的处理.
一个简单的执行逻辑(以无状态重试为例)
整个逻辑参照于类RetryTemplate#doExecute方法,其中 recoveryCallback对象为null,即无failback. state为null,即不是有状态的重试.
- 创建想重试上下文 RetryContext ,即为当前执行准备好相应的数据对象
- 启动相应的拦截器 RetryListener, 以支持在进行实际调用前进行相应的判断,以判断是否应该继续执行操作
- 启动可重试判定 RetryPolicy,判定的条件包括当前执行次数,以及相应的异常是否在期望异常体系之内。
- 执行实际的业务逻辑 RetryCallback,如果成功则直接返回
- 业务执行失败,上下文 RetryContext 补充相应的异常数据
- 同时拦截器 启动失败时拦截回调. RetryListener, 此时的失败回调是void,即仅起到通知作用
- 在需要进行下一次重试的基础之上(即还会进行重试), 启动 RetryPolicy 重试策略,即准备相应的间隔sleep处理
- 在所有重试均结束时,直接Throw最后一次执行异常.
- 完成整个重试逻辑,拦截器 RetryListener 进行onClose回调,即在整个重试体系结束前进行通知回调.
实际业务场景举例
在实际的业务场景当中,对于一个业务方法 doBusiness(), 我们期望对此方法进行重试操作,在调用此方法时,如果throw了一个TimeoutException,则进行重试,重试次数为3,每次重试时间隔1秒。
对于这个业务场景,则相应的配置则如下参考所示:
@Retryable(maxAttempts=3, backoff=@Backoff(1000)) public void doBusiness() { //do your business }
后注
其实spring-retry的整个概念还是很简单的,只不过考虑场景太多,以至于一些源码理解上偏麻烦,但对于整个重试场景来说,spring-retry在模型和接口上的整理还是很值得参考。在一个简单的业务场景中,可以自己发明一个小轮子来完成类似的事务。在一些类似的设计场景,也可以考虑类似的方法和实现.
转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/201805070001.html