Spring基础篇(2)-重试

JAVA && Spring && SpringBoot2.x — 学习目录

每篇一问

当我们调用一个接口时,可能因为网络波动造成了系统之间超时,此时我们应该怎么办?

重试呗~
是的,对于读操作或者支持幂等的写操作,一般我们便可以进行重试操作。那如何进行重试?

不用担心,Spring都处理好了...


正常逻辑和重试机制耦合度较高。基于这个些问题,spring-retry规范了了正常逻辑和重试逻辑,将正常逻辑和重试逻辑解耦。spring-retry是一个开源工具包,该工具把重试操作模板定制化,可以设置重试策略和回退策略。同时,重试执行实例保证线程安全。spring-retry重试可以用Java代码实现也可以用注解@Retryable方式实现,这里spring-retry提倡以注解的方式对方法进行重试。

1、如何使用Spring-Retry进行重试

1. pom文件需要引入的依赖

  
        
            org.springframework.retry
            spring-retry
        
        
            org.aspectj
            aspectjweaver
        

2. 启动类上配置自动加载

在SpringBoot 引入新技术,一般需要2步:
(1)pom 文件加入依赖;
(2)启动类上加上@EnableXXX注解;

  @SpringBootApplication
  @EnableRetry //开启retry重试机制
  public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
}

3. @Retryable和@Recover的使用

@Service
public class RetryService {
    //开启日志控制
    private final static Logger logger = LoggerFactory.getLogger(RetryService.class);
    private final int totalNum = 100;

    /**
     * value:出现Exception.class进行重试,指定特定的异常
     * • include:和value一样,默认为空,当exclude也为空时,默认所以异常
     * • exclude:指定不处理的异常
     * •maxAttempts:最大重试次数,默认3次
     * •backoff:重试等待策略,默认使用@Backoff,@Backoff的value默认为1000L,
     * 我们设置为2000L;multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,
     * 如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5
     *
     * @param num
     * @return
     */
    @Retryable(value = Exception.class, maxAttempts = 3,
            backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public int retry(int num) {
        logger.info("{}减库存开始", LocalTime.now());
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            logger.error("illegal");
        }
        if (num <= 0) {
            throw new IllegalArgumentException("数量不对");
        }
        logger.info("减库存执行结束" + LocalTime.now());
        return totalNum - num;
    }


    /**
     * 作为降级方法调用的注释(重试调用完依旧失败,触发降级)。
     * 参数(可选):合适的Throwable类型的第一个参数(或Throwable的子类型),若是参数为空,只有其他降级注释标注的方法均不复合时,才会执行改方法。(后续可以携带请求参数,但是Throwable必须为第一个参数)
     *  此时该请求参数明确
     * 返回值:@Retryable 方法类型相同的返回值。
     */
    @Recover
    public int recover(IllegalArgumentException e,int num) {
        log.warn("recover减库存失败!!!" + LocalTime.now());
        return totalNum;
    }

    /**
     * 作为降级方法调用的注释(重试调用完依旧失败,触发降级)。
     * 参数(可选):合适的Throwable类型的第一个参数(或Throwable的子类型),若是参数为空,只有其他降级注释标注的方法均不复合时,才会执行改方法。
     * 返回值:@Retryable 方法类型相同的返回值。
     */
    @Recover
    public int recover2() {
        log.warn("recover2减库存失败2222!!!" + LocalTime.now());
        return totalNum;
    }

4. 测试方法

@RunWith(SpringRunner.class)
@SpringBootTest
public class RetryServiceTest {
    @Autowired
    RetryService retryService;

    @Test
    public void testRetry() {
        int i = retryService.retry(-1);
        System.out.println("数据是: "+ i );
    }
}

5. 测试效果

Spring基础篇(2)-重试_第1张图片
image.png

@Retryable注解
被注解的方法发生异常时会重试
value:指定发生的异常进行重试
include:和value一样,默认空,当exclude也为空时,所有异常都重试
exclude:指定异常不重试,默认空,当include也为空时,所有异常都重试
maxAttemps:重试次数,默认3
backoff:重试补偿机制,默认没有。
exceptionExpression:spEL表达式,若配置include或exclude参数那么该配置不会生效,其作用等效于include或exclude。格式#{@bean.methodName(#root)}methodName的返回值为boolean类型。#root是异常类,即用户可以在代码中判断是否进行重试。

    @Retryable(maxAttempts = 2,
            backoff = @Backoff(delay = 2000L, multiplier = 1.5),
            exceptionExpression = "#{@retryService.xxx(#root)}")
    public int retry(int num) {
        log.info("{}减库存开始", LocalTime.now());
        if (num <= 0) {
            throw new RuntimeException("数量不对");
        }
        log.info("减库存执行结束" + LocalTime.now());
        return totalNum - num;
    }
    
    public Boolean xxx(Exception e) {
        //根据异常信息决定是否重试!!!
        log.info("判断是否异常");
        return true;
    }

@Backoff注解
delay:指定延迟后重试
multiplier:指定延迟的倍数,比如delay=5000l,multiplier=2时,第一次重试为5秒后,第二次为10秒,第三次为20秒
@Recover
当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调。


下面咱们讲一下注意事项:

  1. 对于非幂等的读操作,应该禁止使用重试机制;
  2. 使用了@Retryable的方法不能在本类被调用(不能被套嵌使用),不然重试机制不会生效。由于retry用到了aspect增强,所有会有aspect的坑,就是方法内部调用,会使aspect增强失效,那么retry当然也会失效。
  3. 注解方法的异常不能通过try-catch捕获,应该throw出去,被捕获后处理,本质上就是AOP思想的使用。

你可能感兴趣的:(Spring基础篇(2)-重试)