Spring的重试机制

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

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

一、Spring Retry配置

POM引入依赖:

        
            org.springframework.retry
            spring-retry
        

当不清楚引入依赖最新的版本和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 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: 郭秀志 [email protected]
 * @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

你可能感兴趣的:(Spring的重试机制)