Spring家族中有一个小项目是 Spring Retry ,他用来方便地实现重试机制。你的程序在调用远程API的时候,可能因为网络抖动等原因,导致调用失败,这种失败是偶发的,出现了多试几下就行。如果你遇到需要重试多次的情况,倘若只是朴素地写个for循环N次,请求成功就跳出循环返回结果,未免也太粗糙了。Spring已经为这一通用需求造好了轮子。
Spring Retry现在被 Spring Batch 和 Spring Integration 等项目引用,在Spring Boot项目中,我一般直接引入 spring-boot-start-batch
来间接引入Spring Retry(Spring Batch是个批处理工具库,你也许会用得到其中的类,我们有机会再具体研究下),另外它还要配合AOP Starter才能使用,后者你的项目里一般都有。引入Spring Retry后,可以用 加注解声明 的方式,或者 编码 的方式来使用。
先来看看最简单的使用注解:
Spring Retry使用了Spring AOP,一旦通过 @EnableRetry
开启重试的功能,那么 @Retryable
注解对应的方法就会被包装起来。就像上图中的yourMethod方法,如果执行的过程中发生了异常,那么yourMethod就会再次执行,直到次数到了注解参数maxAttempts指定的最大值,如果所有尝试都不成功,异常就会抛出。 @Retryable
注解还有很多其他参数,比如include参数可以指定发生哪些异常会重试,比如exclude参数表示哪些异常不重试,还有backoff参数可以指定一次失败之后,过多长时间再开始下一次重试。
由于Spring AOP的缺陷,你不能在上图中DemoService的内部调用yourMethod方法,否则会失去重试的效果。你只能在DemoService外部调用,或者使用比较tricky的方法来绕过。具体原因可以查看我之前写的讲解AOP的文章。
注解声明式的使用,最终执行的时候也是使用了一套基本的编程API,我们简单看一下如何直接使用这些API:
上图的代码中,我们创建了一个 RetryTemplate
对象,这个对象是重试相关操作的发起者。接下来设置了他的 RetryPolicy
和 BackOffPolicy
,这两个接口,前者定义了什么情况下开始重试,什么情况下停止重试,后者定义了两次重试的时间间隔应该如何确定。
RetryTemplate
中默认的 RetryPolicy
是 SimpleRetryPolicy
,它限制尝试的次数,最大尝试次数是3。如果你想自定义,可以像上图那样自己创建一个 SimpleRetryPolicy
对象,用它设置你想要的最大重试次数,和遇到哪些异常才重试。我建议你显式指定需要重试的异常,比如IO异常,这样可以避免某些情况下进行无谓的重试,浪费时间。
RetryTemplate
中默认的 BackOffPolicy
是 NoBackOffPolicy
,意思就是一旦失败了,就立马重试。如果你觉的需要定义间隔时间,可以使用如下几个:
FixedBackOffPolicy
设定固定的间隔时间 UniformRandomBackOffPolicy
在一个时间区间内[min,max]随机出一个时间间隔 ExponentialBackOffPolicy
让间隔时间按照某种比例增长,比如第一次隔1秒,第二次隔2秒,第三次隔4秒,依此类推。 setInitialInterval
方法设置初始的间隔时间, setMultiplier
设置时间的增长速率,默认是2,意味着每次时间翻倍。你也可以给间隔时间设置一个上限,时间过长 说完了 RetryPolicy
和 BackOffPolicy
,我们再来看看 RetryTemplate
的核心方法execute,他的参数是一个 RetryCallback
对象,其中doWithRetry方法里运行的就是你的业务逻辑,你可以将单独的业务逻辑提取出来,在doWithRetry中调用。代码如下,businessLogic方法独立出来后可以在其他地方复用,execute方法调用显得更简洁,且返回值result就是businessLogic方法执行成功时候的返回值:
doWithRetry方法有个 RetryContext
类型的参数context,它包含了此次重试的信息,比如当前是第几次重试,上次发生的异常等等。一般情况下你不会用到它。
RetryTemplate
的execute方法还有另外一种重载形式,多了一个 RecoveryCallback
对象。它是在重试次数用完的时候调用的,默认情况下用完次数会抛出异常,但是你可能懒得去处理,可以用RecoveryCallback,在所有重试都失败的时候提供个返回值,可以是默认值或者是从其他什么地方拿来的值。
不管是使用注解声明还是使用 RetryTemplate
,他们的效果其实等价的,后者可能更灵活一点,我经常在项目中选用。如果你想看注解是如何被转换成 RetryTemplate
调用的,可以查看 AnnotationAwareRetryOperationsInterceptor
类。
Spring Retry的内容还是挺简单的,Spring生态中有很多这种使常见代码模型简化的工具,如果你都掌握的很好,可以使你的代码显得更健壮更易读。