一眼就能看出来了,这里面就是应该非常熟悉的动态代理机制,这里的 invocation 就是我们的 callChannel 方法:
从代码我们知道,callChannel 方法抛出的异常,在 doWithRetry 方法里面会进行捕获,然后直接扔出去:
这里其实也很好理解的,因为需要抛出异常来触发下一次的重试。
但是这里也暴露了一个 Spring-retry 的弊端,就是必须要通过抛出异常的方式来触发相关业务。
听着好像也是没有毛病,但是你想想一下,假设渠道方说如果我给你返回一个 500 的 ErrorCode,那么你也可以进行重试。
这样的业务场景应该也是比较多的。
如果你要用 Spring-retry 会怎么做?
是不是得写出这样的代码:
if(errorCode==500){ throw new Exception("手动抛出异常"); }
意思就是通过抛出异常的方式来触发重试逻辑,算是一个不是特别优雅的设计吧。
其实根据返回对象中的某个属性来判断是否需要重试对于这个框架来说扩展起来也不算很难的事情。
你想,它这里本来就能拿到返回。只需要提供一个配置的入口,让我们告诉它当哪个对象的哪个字段为某个值的时候也应该进行重试。
当然了,大佬肯定有自己的想法,我这里都是一些不成熟的拙见而已。其实另外的一个重试框架 Guava-Retry,它就支持根据返回值进行重试。
不是本文重点就不扩展了。
接着往下看 while 循环中捕获异常的部分。
里面的逻辑也不复杂,但是下面框起来的部分可以注意一下:
这里又判断了一次是否可以重试,是干啥呢?
是为了执行这行代码:
backOffPolicy.backOff(backOffContext);
它是干啥的?
我也不知道,debug 看一眼,最后会走到这个地方:
org.springframework.retry.backoff.ThreadWaitSleeper#sleep
在这里执行睡眠 1000ms 的操作。
我一下就懂了,这玩意在这里给你留了个抓手,你可以设置重试间隔时间的抓手。然后默认给你赋能 1000ms 后重试的功能。
然后我在 @Retryable 注解里面找到了这个东西:
这玩意一眼看不懂是怎么配置的,但是它上面的注解叫我看看 Backoff 这个玩意。
它长这样:
这东西看起来就好理解多了,先不管其他的参数吧,至少我看到了 value 的默认值是 1000。
我怀疑就是这个参数控制的指定重试间隔,所以我试了一下:
果然是你小子,又让我挖到一个彩蛋。
在 @Backoff 里面,除了 value 参数,还有很多其他的参数,他们的含义分别是这样的:
- delay:重试之间的等待时间(以毫秒为单位)
- maxDelay:重试之间的最大等待时间(以毫秒为单位)
- multiplier:指定延迟的倍数
- delayExpression:重试之间的等待时间表达式
- maxDelayExpression:重试之间的最大等待时间表达式
- multiplierExpression:指定延迟的倍数表达式
- random:随机指定延迟时间
就不一一给你演示了,有兴趣自己玩去吧。
因为丰富的重试时间配置策略,所以也根据不同的策略写了不同的实现:
通过 Debug 我知道了默认的实现是 FixedBackOffPolicy。
其他的实现就不去细研究了,我主要是抓主要链路,先把整个流程打通,之后自己玩的时候再去看这些枝干的部分。
在 Demo 的场景下,等待一秒钟之后再次发起重试,就又会再次走一遍 while 循环,重试的主链路就这样梳理清楚了。
其实我把代码折叠一下,你可以看到就是在 while 循环里面套了一个 try-catch 代码块而已:
这和我们之前写的丑代码的骨架是一样的,只是 Spring-retry 把这部分代码进行扩充并且藏起来了,只给你提供一个注解。
当你只拿到这个注解的时候,你把它当做一个黑盒用的时候会惊呼:这玩意真牛啊。
但是现在当你抽丝剥茧的翻一下源码之后,你就会说:就这?不过如此,我觉得也能写出来啊。
到这里前面抛出的问题中的前两个已经比较清晰了:
问题一:找到它的 for 循环在哪里。
没有 for 循环,但是有个 while 循环,其中有一个 try-catch。
问题二:它是怎么判断应该要重试的?
判断要触发重试机制的逻辑还是非常简单的,就是通过抛出异常的方式触发。
但是真的要不要执行重试,才是一个需要仔细分析的重点。
Spring-retry 有非常多的重试策略,默认是 SimpleRetryPolicy,重试次数为 3 次。
但是需要特别注意的是它这个“3次”是总调用次数为三次。而不是第一次调用失败后再调用三次,这样就共计 4 次了。关于到底调用几次的问题,还是得分清楚才行。
而且也不一定是抛出了异常就肯定会重试,因为 Spring-retry 是支持对指定异常进行处理或者不处理的。
可配置化,这是一个组件应该具备的基础能力。
还是剩下最后一个问题:它是怎么执行到 @Recover 逻辑的?
接着怼源码吧。