扒一扒@Retryable注解,很优雅,有点意思! (1)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 扒一扒@Retryable注解,很优雅,有点意思! (1)

你好呀,我是歪歪。

前几天我 Review 代码的时候发现项目里面有一坨逻辑写的非常的不好,一眼望去简直就是丑陋之极。

我都不知道为什么会有这样的代码存在项目里面,于是我看了一眼提交记录准备叫对应的同事问问,为什么会写出这样的代码。

然后...

那一坨代码是我 2019 年的时候提交的。

我细细的思考了一下,当时好像由于对项目不熟悉,然后其他的项目里面又有一个类似的功能,我就直接 CV 大法搞过来了,里面的逻辑也没细看。

嗯,原来是历史原因,可以理解,可以理解。

image.png

代码里面主要就是一大坨重试的逻辑,各种硬编码,各种辣眼睛的补丁。

特别是针对重试的逻辑,到处都有。所以我决定用一个重试组件优化一波。

今天就带大家卷一下 Spring-retry 这个组件。



丑陋的代码


先简单的说一下丑陋的代码大概长什么样子吧。

给你一个场景,假设你负责支付服务,需要对接外部的一个渠道,调用他们的订单查询接口。

他们给你说:由于网络问题,如果我们之间交互超时了,你没有收到我的任何响应,那么按照约定你可以对这个接口发起三次重试,三次之后还是没有响应,那就应该是有问题了,你们按照异常流程处理就行。

假设你不知道 Spring-retry 这个组件,那么你大概率会写出这样的代码:

image.png

逻辑很简单嘛,就是搞个 for 循环,然后异常了就发起重试,并对重试次数进行检查。

然后搞个接口来调用一下:

image.png

正常调用一次,重试三次,一共可以调用 4 次。在第五次调用的时候抛出异常。

完全符合需求,自测也完成了,可以直接提交代码,交给测试同学了。

非常完美,但是你有没有想过,这样的代码其实非常的不优雅。

你想,如果再来几个类似的“超时之后可以发起几次重试”需求。

那你这个 for 循环是不是得到处的搬来搬去。就像是这样似的,丑陋不堪:

image.png

image.png

但是我现在是一个有代码洁癖的人,这样的代码肯定是不能忍的。

重试应该是一个工具类一样的通用方法,是可以抽离出来的,剥离到业务代码之外,开发的时候我们只需要关注业务代码写的巴巴适适就行了。

那么怎么抽离呢?

你说巧不巧,我今天给你分享这个的东西,就把重试功能抽离的非常的好:

https://github.com/spring-projects/spring-retry

image.png

image.png


所以,我决定带大家扒一扒这个注解。看看别人是怎么把“重试”这个功能抽离成一个组件的,这比写业务代码有意思。

我这篇文章不会教大家怎么去使用 spring-retry,它的功能非常的丰富,写用法的文章已经非常多了。我想写的是,当我会使用它之后,我是怎么通过源码的方式去了解它的。

怎么把它从一个只会用的东西,变成简历上的那一句:翻阅过相关源码。

image.png

没关系,我了解一个技术点的第一步,一定是先搭建出一个非常简单的 Demo。

没有跑过 Demo 的一律当做一无所知处理。


先搭 Demo


我最开始也是对这个注解一无所知的。

所以,对于这种情况,废话少说,先搞个 Demo 跑起来才是王道。

但是你记住搭建 Demo 也是有技巧的:直接去官网或者 github 上找就行了,那里面有最权威的、最简洁的 Demo。

比如 spring-retry 的 github 上的 Quick Start 就非常简洁易懂。


image.png

它分别提供了注解式开发和编程式开发的示例。

我们这里主要看它的注解式开发案例:

image.png

里面涉及到三个注解:

  • @EnableRetry:加在启动类上,表示支持重试功能。
  • @Retryable:加在方法上,就会给这个方法赋能,让它有用重试的功能。
  • @Recover:重试完成后还是不成功的情况下,会执行被这个注解修饰的方法。

看完 git 上的 Quick Start 之后,我很快就搭了一个 Demo 出来。

如果你之前不了解这个组件的使用方法的话,我强烈建议你也搭一个,非常的简单。

首先是引入 maven 依赖:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.3.1</version>
</dependency>

由于该组件是依赖于 AOP 给你的,所以还需要引入这个依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.6.1</version>
</dependency>

然后是代码,就这么一点,就够够的了:

image.png

最后把项目跑起来,调用一笔,确实是生效了,执行了 @Recover 修饰的方法:

image.png

image.png

调用的地方不多,确实也很容易就定位到下面这个关键的类:

org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor

然后在相应的位置打上断点,开始跑程序,进行 debug:

image.png

但是我现在不会这么猴急了,作为一个老程序员,现在就成熟了很多,不会先急着去卷源码,会先多从日志里面挖掘一点东西出来。

我现在遇到这个问题的第一反应就是调整日志级别到 debug:

logging.level.root=debug

修改日志级别重启并再次调用之后,就能看到很多有价值的日志了:

image.png

基于日志,可以直接找到这个地方:

org.springframework.retry.support.RetryTemplate#doExecute

image.png

在这里打上断点进行调试,才是最合适的地方。

这也算是一个调试小技巧吧。以前我经常忽略日志里面的输出,感觉一大坨难得去看,其实仔细去分析日志之后你会发现这里面有非常多的有价值的东西,比你一头扎到源码里面有效多了。

你要是不信,你可以去试着看一下 Spring 事务相关的 debug 日志,我觉得那是一个非常好的案例,打印的那叫一个清晰。

从日志就能推动你不同隔离级别下的 debug 的过程,还能保持清晰的链路,不会有杂乱无序的感觉。

好了,不扯远了。

我们再看看这个日志,这个输出你不觉得很熟悉吗?

这不和刚刚我们前面出现的一张图片神似吗?

image.png

看到这里一丝笑容浮现在我的嘴角:小样,我盲猜你源码里面肯定也写了一个 for 循环。如果循环里面抛出异常,那么就检测是否满足重试条件,如果满足则继续重试。不满足,则执行 @Recover 的逻辑。

要是猜错了,我直接把电脑屏幕给吃了。

好,flag 先立在这里了,接下来我们去撸源码。

等等,先停一下。

如果说我们前面找到了 Debug 第一个断点打的位置,那么真正进入源码调试之前,还有一个非常关键的操作,那就是我之前一再强调的,一定要带着比较具体的问题去翻源码。

而我前面立下的 flag 其实就是我的问题:我先给出一个猜想,再去找它是不是这样实现的,具体到代码上是怎么实现。

所以再梳理了一下我的问题:

  • 1.找到它的 for 循环在哪里。
  • 2.它是怎么判断应该要重试的?
  • 3.它是怎么执行到 @Recover 逻辑的?

现在可以开始发车了。

image.png

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
5月前
|
Java 编译器
你说啥什么?注解你还不会?
你说啥什么?注解你还不会?
63 0
|
5月前
|
Java 数据库连接 数据库
什么时候用@MapperScan 注解?
什么时候用@MapperScan 注解?
139 0
|
Java 测试技术 Spring
关于@RunWith注解的一点问题
IDEA写springboot测试关于@Runwith的小问题
224 0
关于@RunWith注解的一点问题
|
Java 编译器
关于@FunctionalInterface注解
FunctionalInterface
437 0
关于@FunctionalInterface注解
|
存储 JSON Java
一文学会注解的正确使用姿势
一文学会注解的正确使用姿势
一文学会注解的正确使用姿势
|
XML Dubbo Java
duboo注解使用详解
当越来越的的接口与实现类的增加后,duboo的xml配置会越来越多,为了防止几百几千行的代码,减少开发人员配置xml的工作量,使用duboo的注解模式,减少配置多出问题多的可能性!
162 0
duboo注解使用详解
扒一扒@Retryable注解,很优雅,有点意思! (4)
扒一扒@Retryable注解,很优雅,有点意思! (4)
221 0
扒一扒@Retryable注解,很优雅,有点意思! (4)
扒一扒@Retryable注解,很优雅,有点意思! (3)
扒一扒@Retryable注解,很优雅,有点意思! (3)
481 0
扒一扒@Retryable注解,很优雅,有点意思! (3)
|
缓存
扒一扒@Retryable注解,很优雅,有点意思! (2)
扒一扒@Retryable注解,很优雅,有点意思! (2)
307 0
扒一扒@Retryable注解,很优雅,有点意思! (2)