当前位置: 首页 > 工具软件 > Spring-Retry > 使用案例 >

基于注解的spring-retry详解(一文足矣)

荆学民
2023-12-01

介绍

spring retry是用于方法重试的一个功能组件,是基于spring aop实现的,在实际业务中,因为网络原因,请求有时失败,使用一定的重试策略是可以成功的.

核心注解讲解

主要讲解Retryable和Backoff

Retryable

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

	// 用于重试失败的兜底策略,一般不使用这个属性(后面有具体例子)
	String recover() default "";

	
	String interceptor() default "";

	// 支持重试的异常,与include等价,如果value和include都不配置则重试所有
	Class<? extends Throwable>[] value() default {};

	// 需要重试的异常
	Class<? extends Throwable>[] include() default {};

	// 不重试的异常
	Class<? extends Throwable>[] exclude() default {};

	// 重试处的唯一名词定义,不定义则会基于方法签名生成,一般用不到
	String label() default "";

	// 是否有状态的重试(需要学习spring-retry 有状态重试相关,一般用不到)
	boolean stateful() default false;

	// 最大执行次数
	int maxAttempts() default 3;

		
	String maxAttemptsExpression() default "";

	// 重试具体策略(重要)
	Backoff backoff() default @Backoff();

	
	String exceptionExpression() default "";

	// spring-retry监听器配置,此处配置的是监听器在ioc容器中的id,需要了解spring retry的监听器才会使用
	String[] listeners() default {};

}

Backoff

public @interface Backoff {

	// 与delay效果等价,失败后等待多少ms重试下一次
	long value() default 1000;

	// 与valuey效果等价,失败后等待多少ms重试下一次
	long delay() default 0;

	// 重试等待的最大时间与delay配合使用,以下是一些结论
	// 1. 如果@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000,maxDelay = 9000)) 则本例等待时间是1000~9000之间随机
	// 2. 如果配合multiplier使用时,maxDelay<delay,@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000,maxDelay = 900,multiplier=1.5)),则本例子等待时间是1000~30000之间,也就是在multiplier配置的情况下,maxDelay非0,且maxDelay<delay,则maxDelay=30s
	long maxDelay() default 0;
	
	// 可以指数增长重试间隔
	double multiplier() default 0;


	String delayExpression() default "";


	String maxDelayExpression() default "";


	String multiplierExpression() default "";

	// 在使用multiplier时有效,配置为true,每次延迟的时间会有一定的随机
	boolean random() default false;

}

Recover使用

直接看下例,在spring retry中,一个方法如果需要recover策略,只需要在同类中加入一个
方法,方法使用recover标记即可,方法的签名第一个是异常,后面为原方法的参数,如下例
因为test5抛出的是RuntimeException,因此recover5会执行,而recover6不会执行

    @Retryable
    public void test5(int num){
        nothingToDo("test5");
    }
	    @Recover
    public void recover5(RuntimeException exception,int num){
        log.info("recover11: {},{}",exception.getMessage(),num);
    }
	    @Recover
    public void recover6(IllegalArgumentException exception,int num){
        log.info("recover22: {},{}",exception.getMessage(),num);
    }

demo

本例中抛出的异常是: RuntimeException

package com.example.demospringretry;

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.Component;

import java.io.IOException;

/**
 * @author wurui
 * @description: retry demo
 * @copyright copyright © 贝壳找房(北京)科技有限公司
 * @date 2022/2/14
 */
@Component
@Slf4j
public class RetryDemo {

    private Long lastTime = 0L;

    /**
     * 默认重试,可以看到会执行3次,每次间隔1000ms
     */
    @Retryable
    public void test1(){
        nothingToDo("test1");
    }

    private void nothingToDo(String name) {
        long now = System.currentTimeMillis();
        if (lastTime != 0L){
            log.info("distance:{}ms",now - lastTime);
        }
        log.info("{}: try do something",name);
        lastTime = now;
        throw new RuntimeException("");
    }

    /**
     * 可以发现并不会重试
     */
    @Retryable(maxAttempts = 4,value = {IOException.class})
    public void test2(){
        nothingToDo("test2");
    }

    /**
     * 最小1000,最大10000,随机1000-10000之间延迟
     */
    @Retryable(maxAttempts = 7,include = {RuntimeException.class}, backoff = @Backoff(delay = 1000,maxDelay = 10000))
    public void test3(){
        nothingToDo("test3");
    }

    /**
     * 首次延迟1秒,后续为当前延迟*1.5
     */
    @Retryable(maxAttempts = 5,include = {RuntimeException.class}, backoff = @Backoff(delay = 1000,multiplier = 1.5,random = true))
    public void test4(){
        nothingToDo("test4");
    }

    @Retryable
    public void test5(int num){
        nothingToDo("test5");
    }

    @Retryable(maxAttempts = 5,
            backoff= @Backoff(value = 1500, maxDelay = 100000, multiplier = 3))
    public void test6(){
        nothingToDo("test6");
    }

    @Recover
    public void recover5(RuntimeException exception,int num){
        log.info("recover11: {},{}",exception.getMessage(),num);
    }

    @Recover
    public void recover6(IllegalArgumentException exception,int num){
        log.info("recover22: {},{}",exception.getMessage(),num);
    }
}


运行结果

spring retry的delay是通过sleep实现的,大家可以发现等待时间会略有误差
test1:
@Retryable直接加此注解,会使用@BackOff的默认配置,delay就是1000ms,重试次数3次

2022-02-15 10:56:11.612  INFO 14564 --- [           main] com.example.demospringretry.RetryDemo    : test1: try do something
2022-02-15 10:56:12.628  INFO 14564 --- [           main] com.example.demospringretry.RetryDemo    : distance:1016ms
2022-02-15 10:56:12.628  INFO 14564 --- [           main] com.example.demospringretry.RetryDemo    : test1: try do something
2022-02-15 10:56:13.635  INFO 14564 --- [           main] com.example.demospringretry.RetryDemo    : distance:1007ms
2022-02-15 10:56:13.635  INFO 14564 --- [           main] com.example.demospringretry.RetryDemo    : test1: try do something


java.lang.IllegalArgumentException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)

test2:
@Retryable(maxAttempts = 4,value = {IOException.class})
因为只对IOException重试,因此只会执行一次

2022-02-15 11:02:40.417  INFO 22388 --- [           main] com.example.demospringretry.RetryDemo    : test2: try do something


java.lang.IllegalArgumentException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282)

test3
@Retryable(maxAttempts = 7,include = {RuntimeException.class}, backoff = @Backoff(delay = 1000,maxDelay = 10000))
可以看出,delay和maxDelay配合使用时,是在delay~maxDelay之间随机.
如果maxDelay < delay,且multiplier不设置,则直接使用delay延迟,如果multiplier设置了,则会按照multiplier设置的增长幅度增长,maxDelay值是30s

2022-02-15 11:05:02.088  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : test3: try do something
2022-02-15 11:05:03.955  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : distance:1867ms
2022-02-15 11:05:03.955  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : test3: try do something
2022-02-15 11:05:07.723  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : distance:3768ms
2022-02-15 11:05:07.723  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : test3: try do something
2022-02-15 11:05:17.639  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : distance:9916ms
2022-02-15 11:05:17.639  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : test3: try do something
2022-02-15 11:05:20.335  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : distance:2696ms
2022-02-15 11:05:20.335  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : test3: try do something
2022-02-15 11:05:21.368  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : distance:1033ms
2022-02-15 11:05:21.368  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : test3: try do something
2022-02-15 11:05:27.217  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : distance:5849ms
2022-02-15 11:05:27.217  INFO 17696 --- [           main] com.example.demospringretry.RetryDemo    : test3: try do something


java.lang.IllegalArgumentException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282)

test4
@Retryable(maxAttempts = 5,include = {RuntimeException.class}, backoff = @Backoff(delay = 1000,multiplier = 1.5))
第一次重试等待1000,后续为当前等待时间*1.5,本例中一共执行5次,从第二次开始算重试

2022-02-15 11:11:03.710  INFO 8520 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something
2022-02-15 11:11:04.725  INFO 8520 --- [           main] com.example.demospringretry.RetryDemo    : distance:1015ms
2022-02-15 11:11:04.725  INFO 8520 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something
2022-02-15 11:11:06.226  INFO 8520 --- [           main] com.example.demospringretry.RetryDemo    : distance:1501ms
2022-02-15 11:11:06.226  INFO 8520 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something
2022-02-15 11:11:08.489  INFO 8520 --- [           main] com.example.demospringretry.RetryDemo    : distance:2263ms
2022-02-15 11:11:08.489  INFO 8520 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something
2022-02-15 11:11:11.864  INFO 8520 --- [           main] com.example.demospringretry.RetryDemo    : distance:3375ms
2022-02-15 11:11:11.864  INFO 8520 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something


java.lang.IllegalArgumentException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)

如果random=true,则每次的delay时间都有一定幅度的随机,记住random目前测试只在multiplier设置时有效
@Retryable(maxAttempts = 5,include = {RuntimeException.class}, backoff = @Backoff(delay = 1000,multiplier = 1.5,random=true))
可以看到第一次应该是1000ms的,变为了1326ms

2022-02-15 11:13:15.949  INFO 17592 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something
2022-02-15 11:13:17.275  INFO 17592 --- [           main] com.example.demospringretry.RetryDemo    : distance:1326ms
2022-02-15 11:13:17.275  INFO 17592 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something
2022-02-15 11:13:19.224  INFO 17592 --- [           main] com.example.demospringretry.RetryDemo    : distance:1949ms
2022-02-15 11:13:19.224  INFO 17592 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something
2022-02-15 11:13:21.625  INFO 17592 --- [           main] com.example.demospringretry.RetryDemo    : distance:2401ms
2022-02-15 11:13:21.626  INFO 17592 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something
2022-02-15 11:13:25.129  INFO 17592 --- [           main] com.example.demospringretry.RetryDemo    : distance:3504ms
2022-02-15 11:13:25.129  INFO 17592 --- [           main] com.example.demospringretry.RetryDemo    : test4: try do something


java.lang.IllegalArgumentException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)

test5
@Retryable
public void test5(int num){
nothingToDo(“test5”);
}
@Recover
public void recover5(RuntimeException exception,int num){
log.info(“recover11: {},{}”,exception.getMessage(),num);
}
@Recover
public void recover6(IllegalArgumentException exception,int num){
log.info(“recover22: {},{}”,exception.getMessage(),num);
}
因为有recover,因此会把recover方法中的处理返回,如果recover方法不抛出异常,则方法成功调用

2022-02-15 11:18:11.927  INFO 21112 --- [           main] com.example.demospringretry.RetryDemo    : test5: try do something
2022-02-15 11:18:12.945  INFO 21112 --- [           main] com.example.demospringretry.RetryDemo    : distance:1018ms
2022-02-15 11:18:12.945  INFO 21112 --- [           main] com.example.demospringretry.RetryDemo    : test5: try do something
2022-02-15 11:18:13.953  INFO 21112 --- [           main] com.example.demospringretry.RetryDemo    : distance:1008ms
2022-02-15 11:18:13.953  INFO 21112 --- [           main] com.example.demospringretry.RetryDemo    : test5: try do something
2022-02-15 11:18:13.954  INFO 21112 --- [           main] com.example.demospringretry.RetryDemo    : recover11: ,100
 类似资料: