Spring中的@Retryable

WHAT

@Retryable由spring-retry模块提供,在方法或类上添加@Retryable注解可以实现方法调用失败的重试。可以指定失败重试的次数、fallback方法

@Retryable

设置重试的次数、指定需要重试的异常

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
    // 指定拦截方法
	String interceptor() default "";
    // 指定需要重试异常,若未指定异常则重试异常为 Exception.class
	Class<? extends Throwable>[] value() default {};
    // 最大尝试次数(包含第一次
	int maxAttempts() default 3;
    	Class<? extends Throwable>[] include() default {};
	Class<? extends Throwable>[] exclude() default {};
	String label() default "";
	boolean stateful() default false;
	String maxAttemptsExpression() default "";
	Backoff backoff() default @Backoff();
	String exceptionExpression() default "";
}

@Backoff

设置重试的时间间隔,不同值的组合会确定不同的计算间隔方式

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(RetryConfiguration.class)
@Documented
public @interface Backoff {
	long value() default 1000;
    // 若delay=0未配置,则delay=value
	long delay() default 0;
	long maxDelay() default 0;
	double multiplier() default 0;
	String delayExpression() default "";
	String maxDelayExpression() default "";
	String multiplierExpression() default "";
	boolean random() default false;
}

@Recover

重试失败后会进入@Recover注解的方法

WHY

在项目中假设调用的外部服务发生网络异常、服务器故障、死机状况,在这些情况下通常会重试几次调用,假如最终还是失败则会返回特定的内容。如果可能在后续的尝试中会成功,则有重试的必要。你可能会写一段循环代码然后计数来实现重试功能,@Retryable提供了更便捷的方式来实现错误重试

BEFORE:
    int retries = 0;
    long wait = 1;
            while (retries < maxRetries) {
        TimeUnit.SECONDS.sleep(wait);
        // 处理任务
        wait *= 2;
        ++retries;
    }
    
AFTER:
    @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000,multiplier = 1.5,maxDelay = 3000))
    String retryForError();
    @Recover
    String recoverResponse(Exception e);

HOW

@Retryable如何使用?怎么实现的?

  • Requirements
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>${version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${version}</version>
</dependency>

事例

public interface RetryService {

    @Retryable(maxAttempts = 3,
               backoff = @Backoff(delay = 1000,multiplier = 1.5,maxDelay = 3000),
               value = ArithmeticException.class)
    String retryForError();

    @Recover
    String recoverResponse(ArithmeticException e);
}

public class RetryServiceImpl implements RetryService {

    @Override
    public String retryForError() {
        System.out.println("retryForError");
        int a = 10 / 0;
        return "Success";
    }

    @Override
    public String recoverResponse(ArithmeticException e) {
        return "RecoverResponse Success";
    }
}


@RestController
public class RetryController {

    @Resource
    private RetryService retryService;

    @GetMapping("/retry")
    public String retryService() {
        return retryService.retryForError();
    }
}

原理

AOP

  1. RetryConfiguration 创建pointcut(Retryable注解), 创建Advice(AnnotationAwareRetryOperationsInterceptor)
  2. AnnotationAwareRetryOperationsInterceptor 委托给默认的 RetryOperationsInterceptor

BackOffPolicy

@Backoff

  1. multiplier > 0 and random = false
initialInterval = delay
maxInterval = maxDelay > initialInterval ? maxDelay : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL(30000毫秒)
		public synchronized long getSleepAndIncrement() {
			long sleep = this.interval;
			if (sleep > maxInterval) {
				sleep = maxInterval;
			}
			else {
				this.interval = getNextInterval();
			}
			return sleep;
		}
        
 protected long getNextInterval() {
			return (long) (this.interval * this.multiplier);
		}
  1. multiplier > 0 and random = true
		public synchronized long getSleepAndIncrement() {
            // 1 中方法
			long next = super.getSleepAndIncrement();
			next = (long) (next * (1 + r.nextFloat() * (getMultiplier() - 1)));
			return next;
		}
  1. maxDelay > initialInterval
			long delta = maxDelay==initialInterval ? 0 : random.nextInt((int) (maxDelay - minBackOffPeriod));
			sleeper.sleep(minBackOffPeriod + delta );
  1. 其他
sleeper.sleep(delay);

注意点

  1. @Recover只对同类中的@Retryable生效
  2. 多个@Recover方法,同一种异常参数。相当于讲这些方法(Method)放在Set中,取出的第一个@Recover方法将会是fallback方法
  3. 多个@Recover方法,不同种异常参数。若@Recover方法参数距离@Retryable方法抛出的异常最近(根据重试的最后一次抛出的异常作为查找依据)则此@Recover方法将会是fallback方法。
RecoverAnnotationRecoveryHandler.findClosestMatch

参考

我的博客

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值