悟纤:最近我看到自己之前的try/catch、while代码进行请求的重试,看着很不舒服。
师傅:确实了,为师以前也是写出过这样的一堆难看的代码。
悟纤:那师傅这个事情有解吗?
师傅:徒儿,你只要记住一个问题,但凡一个问题是普遍的问题,那么一定会有解决方案的。
悟纤:还能这么思考,棒极了。
师傅:那你去找找解决方案,输出文章让大家一起学习下呗。
悟纤:好勒,马上行动起来~~~
导读
不小心看到很久很久的历史代码:
这个代码逻辑实现了网络异常或者没有获取到想要的状态,就根据一定的策略进行重试。
如果代码中很多地方都有这样的代码,那将是一场灾难。
对于上面的场景Spring-Retry就能轻松的搞定。
一、何为Spring-Retry
在实际工作中,重处理是一个非常常见的场景。通常来说,会用try/catch,while循环之类的语法来进行重处理,但是这样的做法缺乏统一性,并且不是很方便,要多写很多代码。然而spring-retry却可以通过注解,在不入侵原有业务逻辑代码的方式下,优雅的实现重处理功能。
1.1 使用场景
重处理场景很多,比如:
(1)发送消息失败。
(2)调用远程服务失败。
(3)争抢锁失败。
1.2 Spring-Retry是什么?
spring-retry是是spring提供的一个重试框架,原本自己实现的重试机制,现在spring帮封装好提供更加好的编码体验。
二、Spring-Retry基本使用
2.1 基本使用
基本思路是引入spring-retry,由于spring-retry使用到了aop,所以也要把aop的依赖进行添加。
大体使用步骤:
(1)创建一个项目
(2)添加依赖
(3)启用Spring-Retry
(4)使用@Retryable
(5)测试
2.1 创建一个项目
使用idea创建项目springboot-retry-demo。
2.2 添加依赖
在pom.xml文件中添加依赖:
<span style="color:#333333"><span style="background-color:rgba(0, 0, 0, 0.03)"><code><span style="color:#afafaf"><?xml version="1.0" encoding="UTF-8"?></span></code><code><span style="color:#0e9ce5"><<span style="color:#0e9ce5">project</span> <span style="color:#0e9ce5">xmlns</span>=<span style="color:#dd1144">"http://maven.apache.org/POM/4.0.0"</span> <span style="color:#0e9ce5">xmlns:xsi</span>=<span style="color:#dd1144">"http://www.w3.org/2001/XMLSchema-instance"</span></span></code><code> xsi:schemaLocation=<span style="color:#dd1144">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">modelVersion</span>></span>4.0.0<span style="color:#0e9ce5"></<span style="color:#0e9ce5">modelVersion</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">parent</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">groupId</span>></span>org.springframework.boot<span style="color:#0e9ce5"></<span style="color:#0e9ce5">groupId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">artifactId</span>></span>spring-boot-starter-parent<span style="color:#0e9ce5"></<span style="color:#0e9ce5">artifactId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">version</span>></span>2.7.6<span style="color:#0e9ce5"></<span style="color:#0e9ce5">version</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">relativePath</span>/></span> <span style="color:#afafaf"><em><!-- lookup parent from repository --></em></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">parent</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">groupId</span>></span>com.kfit<span style="color:#0e9ce5"></<span style="color:#0e9ce5">groupId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">artifactId</span>></span>springboot-retry-demo<span style="color:#0e9ce5"></<span style="color:#0e9ce5">artifactId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">version</span>></span>0.0.1-SNAPSHOT<span style="color:#0e9ce5"></<span style="color:#0e9ce5">version</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">name</span>></span>springboot-retry-demo<span style="color:#0e9ce5"></<span style="color:#0e9ce5">name</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">description</span>></span>springboot-retry-demo<span style="color:#0e9ce5"></<span style="color:#0e9ce5">description</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">properties</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">java.version</span>></span>1.8<span style="color:#0e9ce5"></<span style="color:#0e9ce5">java.version</span>></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">properties</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">dependencies</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">dependency</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">groupId</span>></span>org.springframework.boot<span style="color:#0e9ce5"></<span style="color:#0e9ce5">groupId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">artifactId</span>></span>spring-boot-starter-web<span style="color:#0e9ce5"></<span style="color:#0e9ce5">artifactId</span>></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">dependency</span>></span></code><code></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">dependency</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">groupId</span>></span>org.springframework.retry<span style="color:#0e9ce5"></<span style="color:#0e9ce5">groupId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">artifactId</span>></span>spring-retry<span style="color:#0e9ce5"></<span style="color:#0e9ce5">artifactId</span>></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">dependency</span>></span></code><code></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">dependency</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">groupId</span>></span>org.springframework.boot<span style="color:#0e9ce5"></<span style="color:#0e9ce5">groupId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">artifactId</span>></span>spring-boot-starter-aop<span style="color:#0e9ce5"></<span style="color:#0e9ce5">artifactId</span>></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">dependency</span>></span></code><code></code><code></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">dependency</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">groupId</span>></span>org.springframework.boot<span style="color:#0e9ce5"></<span style="color:#0e9ce5">groupId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">artifactId</span>></span>spring-boot-starter-test<span style="color:#0e9ce5"></<span style="color:#0e9ce5">artifactId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">scope</span>></span>test<span style="color:#0e9ce5"></<span style="color:#0e9ce5">scope</span>></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">dependency</span>></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">dependencies</span>></span></code><code></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">build</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">plugins</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">plugin</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">groupId</span>></span>org.springframework.boot<span style="color:#0e9ce5"></<span style="color:#0e9ce5">groupId</span>></span></code><code> <span style="color:#0e9ce5"><<span style="color:#0e9ce5">artifactId</span>></span>spring-boot-maven-plugin<span style="color:#0e9ce5"></<span style="color:#0e9ce5">artifactId</span>></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">plugin</span>></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">plugins</span>></span></code><code> <span style="color:#0e9ce5"></<span style="color:#0e9ce5">build</span>></span></code><code></code><code><span style="color:#0e9ce5"></<span style="color:#0e9ce5">project</span>></span></code><code></code></span></span>
2.3 启用Spring-Retry
在启动类添加注解@EnableRetry启用Spring-Retry:
<span style="color:#333333"><span style="background-color:rgba(0, 0, 0, 0.03)"><code><span style="color:#afafaf">@SpringBootApplication</span></code><code><span style="color:#afafaf">@EnableRetry</span></code><code><span style="color:#ca7d37">public</span> <span style="color:#ca7d37">class</span> <span style="color:#0e9ce5">SpringbootRetryDemoApplication</span> {</code><code> <span style="color:#ca7d37">public</span> <span style="color:#ca7d37">static</span> <span style="color:#ca7d37">void</span> <span style="color:#dd1144">main</span>(String[] args) {</code><code> SpringApplication.run(SpringbootRetryDemoApplication.class, args);</code><code> }</code><code>}</code><code></code></span></span>
2.4 使用@Retryable
在需要重试的方法添加@Retryable注解:
<span style="color:#333333"><span style="background-color:rgba(0, 0, 0, 0.03)"><code><span style="color:#ca7d37">package</span> <span style="color:#ca7d37">com</span>.kfit.demo;</code><code><span style="color:#ca7d37">import</span> <span style="color:#ca7d37">org</span>.springframework.retry.annotation.Retryable;</code><code><span style="color:#ca7d37">import</span> <span style="color:#ca7d37">org</span>.springframework.stereotype.Service;</code><code><span style="color:#ca7d37">import</span> <span style="color:#ca7d37">java</span>.util.Date;</code><code></code><code>@<span style="color:#ca7d37">Service</span></code><code>public class Demo1Service {</code><code> @<span style="color:#ca7d37">Retryable</span></code><code> public void call() {</code><code> <span style="color:#ca7d37">System</span>.out.println("准备发起<span style="color:#ca7d37">RPC</span>调用..."+<span style="color:#ca7d37">new</span> <span style="color:#ca7d37">Date</span>());</code><code> <span style="color:#ca7d37">throw</span> <span style="color:#ca7d37">new</span> <span style="color:#ca7d37">RuntimeException</span>("<span style="color:#ca7d37">RPC</span>调用异常");</code><code> }</code><code>}</code><code></code></span></span>
2.5 测试
编写测试类或者在controller调用service进行测试,可以在控制台看到结果:
基本使用是不是很简单~ 从这里可以看出最大重试次数为3次。
三、Spring-Retry进阶使用
基本使用很简单,实际项目中需要思考的会更多,所以需要了解@Retryable可以配置的属性。
3.1 常用属性
看下常见的属性:
<span style="color:#333333"><span style="background-color:rgba(0, 0, 0, 0.03)"><code><span style="color:#afafaf"><em>/**</em></span></code><code><span style="color:#afafaf"><em> * value:抛出指定异常才会重试</em></span></code><code><span style="color:#afafaf"><em> * include:和value一样,默认为空,当exclude也为空时,默认所有异常</em></span></code><code><span style="color:#afafaf"><em> * exclude:指定不处理的异常</em></span></code><code><span style="color:#afafaf"><em> * maxAttempts:最大重试次数,默认3次</em></span></code><code><span style="color:#afafaf"><em> * backoff:重试等待策略,</em></span></code><code><span style="color:#afafaf"><em> * 默认使用<span style="color:#dd1144">@Backoff</span>,<span style="color:#dd1144">@Backoff</span>的value默认为1000L,我们设置为2000; 以毫秒为单位的延迟(默认 1000)</em></span></code><code><span style="color:#afafaf"><em> * multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。</em></span></code><code><span style="color:#afafaf"><em> * <span style="color:#dd1144">@return</span></em></span></code><code><span style="color:#afafaf"><em> */</em></span></code><code><span style="color:#afafaf">@Retryable</span>(value = Exception.class,maxAttempts = <span style="color:#0e9ce5">3</span>,backoff = <span style="color:#afafaf">@Backoff</span>(delay = <span style="color:#0e9ce5">2000</span>,multiplier = <span style="color:#0e9ce5">1.5</span>))</code><code><span style="color:#ca7d37">public</span> <span style="color:#ca7d37">void</span> <span style="color:#dd1144">call</span>() {</code><code> System.out.println(<span style="color:#dd1144">"准备发起RPC调用..."</span>+<span style="color:#ca7d37">new</span> Date());</code><code> <span style="color:#ca7d37">throw</span> <span style="color:#ca7d37">new</span> RuntimeException(<span style="color:#dd1144">"RPC调用异常"</span>);</code><code>}</code><code></code></span></span>
* value:抛出指定异常才会重试
* include:和value一样,默认为空,当exclude也为空时,默认所有异常
* exclude:指定不处理的异常
* maxAttempts:最大重试次数,默认3次
* backoff:重试等待策略,
* 默认使用@Backoff,@Backoff的value默认为1000L,我们设置为2000; 以毫秒为单位的延迟(默认 1000)
* multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。
3.2 重试耗尽回调
当重试耗尽时,RetryOperations可以将控制传递给另一个回调,即RecoveryCallback。Spring-Retry还提供了@Recover注解,用于@Retryable重试失败后处理方法。如果不需要回调方法,可以直接不写回调方法,那么实现的效果是,重试次数完了后,如果还是没成功没符合业务判断,就抛出异常。
<span style="color:#333333"><span style="background-color:rgba(0, 0, 0, 0.03)"><code>@Retryable(<span style="color:#ca7d37">value</span> = Exception.class,maxAttempts = <span style="color:#0e9ce5">3</span>,backoff = @Backoff(delay = <span style="color:#0e9ce5">2000</span>,multiplier = <span style="color:#0e9ce5">1.5</span>),recover = <span style="color:#dd1144">"recover"</span>)</code><code><span style="color:#ca7d37">public</span> <span style="color:#ca7d37">void</span> <span style="color:#dd1144">call</span>() {</code><code> System.<span style="color:#ca7d37">out</span>.println(<span style="color:#dd1144">"准备发起RPC调用..."</span>+<span style="color:#ca7d37">new</span> Date());</code><code> <span style="color:#ca7d37">throw</span> <span style="color:#ca7d37">new</span> RuntimeException(<span style="color:#dd1144">"RPC调用异常"</span>);</code><code>}</code><code></code><code>@Recover</code><code><span style="color:#ca7d37">public</span> <span style="color:#ca7d37">void</span> <span style="color:#dd1144">recover</span>(Exception e) {</code><code> System.<span style="color:#ca7d37">out</span>.println(<span style="color:#dd1144">"recover-->记日志到数据库 或者调用其余的方法"</span>);</code><code>}</code><code></code></span></span>
当有多个的时候,可以通过属性recover来指定要回调的方法,执行结果如下:
这里会发现如果定义了回调方法,那么就不会抛出“RPC调用异常”信息了。
3.3 RetryTemplate的使用
Spring-Retry也提供了编码的方式RetryTemplate,先注入这个类:
<span style="color:#333333"><span style="background-color:rgba(0, 0, 0, 0.03)"><code><span style="color:#afafaf">@Bean</span></code><code><span style="color:#ca7d37">public</span> RetryTemplate <span style="color:#dd1144">retryTemplate</span>() {</code><code> RetryTemplate retryTemplate = <span style="color:#ca7d37">new</span> RetryTemplate();</code><code></code><code> SimpleRetryPolicy retryPolicy = <span style="color:#ca7d37">new</span> SimpleRetryPolicy(); <span style="color:#afafaf"><em>//设置重试策略</em></span></code><code> retryPolicy.setMaxAttempts(<span style="color:#0e9ce5">3</span>);</code><code> retryTemplate.setRetryPolicy(retryPolicy);</code><code></code><code></code><code> FixedBackOffPolicy fixedBackOffPolicy = <span style="color:#ca7d37">new</span> FixedBackOffPolicy(); <span style="color:#afafaf"><em>//设置退避策略</em></span></code><code> fixedBackOffPolicy.setBackOffPeriod(<span style="color:#0e9ce5">2000L</span>);</code><code> retryTemplate.setBackOffPolicy(fixedBackOffPolicy);</code><code></code><code> <span style="color:#ca7d37">return</span> retryTemplate;</code><code>}</code><code></code></span></span>
然后进行使用:
<span style="color:#333333"><span style="background-color:rgba(0, 0, 0, 0.03)"><code><span style="color:#ca7d37">public</span> <span style="color:#ca7d37">void</span> call(){</code><code> <span style="color:#ca7d37">try</span> {</code><code> retryTemplate.execute(<span style="color:#ca7d37">new</span> RetryCallback<<span style="color:#ca7d37">Object</span>, IllegalAccessException>() {</code><code> <span style="color:#afafaf">@Override</span></code><code> <span style="color:#ca7d37">public</span> <span style="color:#ca7d37">Object</span> doWithRetry(RetryContext context) throws IllegalAccessException {</code><code> System.out.println(<span style="color:#dd1144">"准备发起RPC调用..."</span>+<span style="color:#ca7d37">new</span> <span style="color:#ca7d37">Date</span>());</code><code> <span style="color:#ca7d37">throw</span> <span style="color:#ca7d37">new</span> RuntimeException(<span style="color:#dd1144">"RPC调用异常"</span>);</code><code> <span style="color:#afafaf"><em>//return null;</em></span></code><code> }</code><code> });</code><code> } <span style="color:#ca7d37">catch</span> (IllegalAccessException e) {</code><code> <span style="color:#ca7d37">throw</span> <span style="color:#ca7d37">new</span> RuntimeException(e);</code><code> }</code><code>}</code><code></code></span></span>
总结
spring-retry通过注解,在不入侵原有业务逻辑代码的方式下,优雅的实现重处理功能。
如果重试多次失败之后,尽量进行回调处理,将这一次的失败信息进行记录,方便后续跟进和优化代码。