Spring的重试机制

有些场景需要我们对一些异常情况下面的任务进行重试,比如:调用远程的RPC/RestTemplate或者Feign服务,可能由于网络抖动出现第一次调用失败,尝试几次就可以恢复正常。当然调用内部的其他服务也会遇到调用失败的情况,这时候就需要通过一些方法来进行重试,比如通过while循环手动重复调用或是通过JDK/CGLib动态代理的方式来进行重试。但是这种方法比较笨重,且对原有逻辑代码的侵入性比较大。

Spring已经为我们提供了封装好的重试功能,spring-retry是spring提供的一个重试框架,使我们可以通过@Retryable@Recover注解来完成重试和重试失败后的回调。

一、Spring Retry配置

POM引入依赖:

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

当不清楚引入依赖最新的版本和groupId的时候,也可以在IDEA中通过它的提示快速添加:
添加retry依赖

二、启动类

在Spring Boot 应用入口启动类,也就是配置类的上面加上@EnableRetry注解,表示让重试机制生效。

启动类增加retry注解

三、编写Controller

简单的Controller,其注入RestTemplate来调用其他服务接口 。代码中被调用的http://www.guo.com:8080/v5/packageIndex/findByState/60服务没启动,所以会抛出404异常,是为了触发重试机制。

@RestController
@Slf4j
@RequestMapping(value = "/rest")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value = "/restTemplate")
    @Retryable(value = RestClientException.class, maxAttempts = 3,
            backoff = @Backoff(delay = 5000L, multiplier = 2))
    public JsonResult<String> findStateByStateFromPackageService() {
        log.info("发起远程API请求");
        String response = null;
      
        response = restTemplate.getForObject("http://www.guo.com:8080/v5/packageIndex/findByState/60", String.class);
      
        log.info("Rest请求数据:" + response);
        return JsonResult.of(response, true, "成功调用");
    }

}
  • @Retryable注解的方法在发生异常时会重试,参数说明:
    value:当指定异常发生时会进行重试 ,HttpClientErrorException是RestClientException的子类。如果所有异常都进行重试,改成Exception.class
    include:和value一样,默认空。如果 exclude也为空时,所有异常都重试
    exclude:指定异常不重试,默认空。如果 include也为空时,所有异常都重试
    maxAttemps:最大重试次数,默认3
    backoff:重试等待策略,默认空
  • @Backoff注解为重试等待的策略,参数说明:
    delay:指定重试的延时时间,默认为1000毫秒
    multiplier:指定延迟的倍数,比如设置delay=5000,multiplier=2时,第一次重试为5秒后,第二次为10(5x2)秒,第三次为20(10x2)秒。

四、启动服务进行测试

启动当前调用方服务后,向http://localhost:8085/rest/restTemplate发起请求,结果如下:

2020-10-04 12:23:39 [http-nio-8085-exec-1] INFO  c.RestTemplateController:128 -发起远程API请求
2020-10-04 12:23:48 [http-nio-8085-exec-1] INFO  c.RestTemplateController:128 -发起远程API请求
2020-10-04 12:24:02 [http-nio-8085-exec-1] INFO  c.RestTemplateController:128 -发起远程API请求
2020-10-04 12:24:06 [http-nio-8085-exec-1] ERROR o.a.c.c.C.[.[localhost].[/].[dispatcherServlet]:175 -Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/v5/packageIndex/findByState/60": Connect to localhost:8080 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect; nested exception is org.apache.http.conn.HttpHostConnectException: Connect to localhost:8080 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect] with root cause
java.net.ConnectException: Connection refused: connect
    at java.base/java.net.PlainSocketImpl.waitForConnect(Native Method)

从结果可以看出:
第一次请求失败之后,延迟后重试
第二次请求失败之后,延迟后重试
第三次请求失败之后,抛出异常

五、@Recover注解

当重试次数达到设置的次数的时候,还是失败抛出异常,执行@Recover注解的回调函数。
新建一个service提供重试和recover方法。

package com.pay.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import java.time.LocalTime;

/**
 * @ClassName: PayService
 * @Description: 模拟库存扣减的service,实现扣减业务的可重试以及多长尝试后失败的回调处理。
 * @author: 郭秀志 jbcode@126.com
 * @date: 2020/10/4 12:43
 * @Copyright:
 */

@Service
@Slf4j
public class PayService {

    private final int totalNum = 53;

    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public int minGoodsnum(int num) throws Exception {
        log.info("减库存开始" + LocalTime.now());
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            log.error("illegal operation");
        }
        if (num <= 0) {
            throw new IllegalArgumentException("数量不对");
        }
        log.info("减库存执行结束" + LocalTime.now());
        return totalNum - num;
    }

    @Recover
    public int recover(Exception e) {
        log.warn("[recover method]减库存失败!!!" + LocalTime.now());
        //记日志到数据库
        //发送异常的邮件通知
        return totalNum;
    }
}

controller增加调用上面service的逻辑

    @Autowired
    private PayService payService;

    @GetMapping("/retry")
    public String getNum() throws Exception {
        int i = payService.minGoodsnum(-1);
        System.out.println("====" + i);
        return "succeess";
    }

测试recover,访问http://localhost:8085/rest/retry,控制台打印信息:

2020-10-04 12:49:54 [http-nio-8085-exec-1] INFO  PayService:27 -减库存开始12:49:54.328000400
2020-10-04 12:49:54 [http-nio-8085-exec-1] ERROR PayService:31 -illegal operation
2020-10-04 12:49:56 [http-nio-8085-exec-1] INFO  PayService:27 -减库存开始12:49:56.337155100
2020-10-04 12:49:56 [http-nio-8085-exec-1] ERROR PayService:31 -illegal operation
2020-10-04 12:49:59 [http-nio-8085-exec-1] INFO  PayService:27 -减库存开始12:49:59.339616100
2020-10-04 12:49:59 [http-nio-8085-exec-1] ERROR PayService:31 -illegal operation
2020-10-04 12:49:59 [http-nio-8085-exec-1] WARN  PayService:42 -[recover method]减库存失败!!!12:49:59.341657600
====53
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342