spring Retryable注解实现重试详解

这篇文章主要介绍了spring Retryable注解实现重试详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

spring-boot:1.5.3.RELEASE,spring-retry-1.2.0.RELEASE

使用方法

引入pom

// 版本号继承spring-boot依赖管理的pom org.springframework.retryspring-retryorg.aspectjaspectjweaver

启用重试

@Configuration @ImportResource(locations = { "classpath*:spring/app-context-*" }) @EnableRetry public class AppContext { }

注解需要重试的方法

@Retryable(value = RuntimeException.class, maxAttempts = 3,backoff = @Backoff(delay = 10L, multiplier = 1)) public boolean myRetryableMethod(){ ... }

注解属性含义

Retryable

@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Retryable { /** * 为重试方法应用重试拦截器的bean名称。与其他属性互斥 */ String interceptor() default ""; /** * 可以重试的异常类型。与includes属性同义。认值为空(并且如果exclude也是空的话, * 所有的异常都会重试) */ Class extends Throwable>[] value() default {}; /** * 同上 */ Class extends Throwable>[] include() default {}; /** * 与include含义相反 */ Class extends Throwable>[] exclude() default {}; /** * 统计报告的唯一标签。如果没有提供,调用者可以选择忽略它,或者提供一个认值。 * * @return the label for the statistics */ String label() default ""; /** * 标识重试有状态的:即异常重新抛出,但是重试策略使用相同的策略应用于后续的具有相同参数的 * 调用。如果为false那么可重试的异常不会重新抛出。 */ boolean stateful() default false; /** * 尝试的最大次数(包含第一次失败),认为3 */ int maxAttempts() default 3; /** * 返回一个求尝试最大次数值的表达式(包含第一次失败),认为3 * 重写 {@link #maxAttempts()}。 * @since 1.2 */ String maxAttemptsExpression() default ""; /** * 为正重试的动作指定backoff属性认没有backoff,但是在两次尝试之间暂定一下是一个很好的想法 * (即使代价是阻塞线程) */ Backoff backoff() default @Backoff(); /** * 在{@code SimpleRetryPolicy.canRetry()}返回true之后指定一个计算表达式 - 可用来有条件的取消重试。 * 仅在调用抛出一个异常后。求值的root对象为上一次的异常 {@code Throwable}。 * 可以引用上下文中的其他beans。 * 例如: * {@code "message.contains('you can retry this')"}. * and * {@code "@someBean.shouldRetry(#root)"}. * @since 1.2 */ String exceptionExpression() default ""; }

Backoff

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(RetryConfiguration.class) @Documented public @interface Backoff { /** * 与 {@link #delay()} 属性同义 * * 返回延迟多少毫秒后重试(认为1000毫秒) */ long value() default 1000; /** * 一个标准的再重试周期。在指数函数情况下用作初始值,在始终如一的情况下(固定周期值情况) * 用作最小值。 * @return the initial or canonical backoff period in milliseconds (default 1000)??? */ long delay() default 0; /** * 重试之间最大等待(毫秒)时间。如果小于 {@link #delay()} 则忽略。 * @return the maximum delay between retries (default 0 = ignored) */ long maxDelay() default 0; /** * 如果是正数,则用于生成下次再重试等待时间的乘数。 * 返回一个乘数用于计算下次再重试延迟(认为0忽略) */ double multiplier() default 0; /** * 标准再重试周期求值表达式。在指数情况下用作初始值,始终如一的情况下用作最小值。 * 重写 {@link #delay()}. * @since 1.2 */ String delayExpression() default ""; /** * 在重试之间最大等待(毫秒)数的求值表达式。 * 如果小于 {@link #delay()} 则忽略。 * 重写 {@link #maxDelay()} * 认为0,忽略 * @since 1.2 */ String maxDelayExpression() default ""; /** * 表达式求值作为生成下次再重试延迟的乘数 * 重写 {@link #multiplier()}。 * @since 1.2 */ String multiplierExpression() default ""; /** * 在指数情况下 ({@link #multiplier()} > 0) 设置该值为true将使再重试延迟随机化, * 使最大延迟为先前延迟的乘数倍数,并使这两个延迟值之间分布均匀。 * 认为false */ boolean random() default false; }

案例

认retry

@Component public class MyTask { @Retryable public void doExecute(){ System.out.println("## current Date:" + new Date()); throw new RuntimeException("my test"); } }

输出结果

## current Date:Sat Aug 29 21:54:55 CST 2020 ## current Date:Sat Aug 29 21:54:56 CST 2020 ## current Date:Sat Aug 29 21:54:57 CST 2020 2020-08-29 21:55:00,319 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection Exception in thread "main" java.lang.RuntimeException: my test ...

stateful

源码相同,注解增加属性配置

@Retryable( stateful = true )

public void doExecute(){

输出结果

## current Date:Sat Aug 29 21:58:56 CST 2020 2020-08-29 21:58:57,557 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection Exception in thread "main" java.lang.RuntimeException: my test // 没有重新抛出异常触发重试

该参数为false时会重试3次后抛出异常,重试期间不会重新抛出异常。参数为true时则重试期间也会重新抛出异常导致重试失败不再继续重试

backoff.multiplier

注解属性配置

@Retryable( backoff = @Backoff( delay = 1000, multiplier = 2), maxAttempts = 10)

输出结果

## current Date:Sat Aug 29 23:06:50 CST 2020 ## current Date:Sat Aug 29 23:06:51 CST 2020 ## current Date:Sat Aug 29 23:06:53 CST 2020 ## current Date:Sat Aug 29 23:06:57 CST 2020 ## current Date:Sat Aug 29 23:07:05 CST 2020 ## current Date:Sat Aug 29 23:07:21 CST 2020 ## current Date:Sat Aug 29 23:07:51 CST 2020 ## current Date:Sat Aug 29 23:08:21 CST 2020 ## current Date:Sat Aug 29 23:08:51 CST 2020 ## current Date:Sat Aug 29 23:09:21 CST 2020 2020-08-29 23:09:21,949 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection Exception in thread "main" java.lang.RuntimeException: my test

乘数正确,指数型增长,第1次延迟1s

第2次,上次延迟1s乘以乘数2=延迟2s

第3次,上次延迟2s乘以乘数2=延迟4s

指数增长,如果没有指定则为始终如一的固定间隔延迟类型。新版本已经增加了各种类型单独的属性配置的模板构建者:

RetryTemplate.builder() .maxAttempts(10) .exponentialBackoff(100, 2, 10000) .retryOn(IOException.class) .traversingCauses() .build(); RetryTemplate.builder() .fixedBackoff(10) .withinMillis(3000) .build(); RetryTemplate.builder() .infiniteRetry() .retryOn(IOException.class) .uniformRandomBackoff(1000, 3000) .build();

backoff.random

测试代码

@Component public class MyTask { private Long lastTime = null; @Retryable( backoff = @Backoff( delay = 1000, multiplier = 2, random = true), maxAttempts = 10) public void doExecute(){ if (lastTime == null) { lastTime = System.currentTimeMillis(); } System.out.println("## actual delay:" + (System.currentTimeMillis() - lastTime) ); RuntimeException runtimeException = new RuntimeException("my test"); throw runtimeException; } }

输出结果

## current Date:Sat Aug 29 22:53:10 CST 2020

## current Date:Sat Aug 29 22:53:11 CST 2020

## current Date:Sat Aug 29 22:53:14 CST 2020

## current Date:Sat Aug 29 22:53:20 CST 2020

## current Date:Sat Aug 29 22:53:29 CST 2020

## current Date:Sat Aug 29 22:53:51 CST 2020

## current Date:Sat Aug 29 22:54:41 CST 2020

## current Date:Sat Aug 29 22:55:25 CST 2020

## current Date:Sat Aug 29 22:56:11 CST 2020

## current Date:Sat Aug 29 22:57:01 CST 2020

2020-08-29 22:57:01,617 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection

Exception in thread "main" java.lang.RuntimeException: my test

延迟更加随机化,由于是最大延迟为之前延迟的乘数的倍数,所以看不出规律。它的使用场景是使延迟更加随机

exceptionExpression

测试代码

@Component public class MyTask { private Long lastTime = null; public boolean canRetry(RuntimeException runtimeException) { System.out.println("canRetry:"+runtimeException.hashCode()); return true; } @Retryable(exceptionExpression = "#{@myTask.canRetry(#root)}", backoff = @Backoff(delay = 1000, multiplier = 2, random = true)) public void doExecute() { if (lastTime == null) { lastTime = System.currentTimeMillis(); } System.out.println("## actual delay:" + (System.currentTimeMillis() - lastTime)); RuntimeException runtimeException = new RuntimeException("my test"); System.out.println("doExecute:"+runtimeException.hashCode()); throw runtimeException; } }

输出结果

## actual delay:0 doExecute:626562869 2020-08-29 23:50:49,905 DEBUG [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:28 public boolean com.dianwoda.billing.settle.task.MyTask.canRetry(java.lang.RuntimeException) execute with datasource is master canRetry:626562869 2020-08-29 23:50:49,906 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection 2020-08-29 23:50:51,335 DEBUG [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:28 public boolean com.dianwoda.billing.settle.task.MyTask.canRetry(java.lang.RuntimeException) execute with datasource is master canRetry:626562869 2020-08-29 23:50:51,336 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection ## actual delay:1450 doExecute:90418597 2020-08-29 23:50:51,337 DEBUG [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:28 public boolean com.dianwoda.billing.settle.task.MyTask.canRetry(java.lang.RuntimeException) execute with datasource is master canRetry:90418597 2020-08-29 23:50:51,338 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection 2020-08-29 23:50:53,620 DEBUG [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:28 public boolean com.dianwoda.billing.settle.task.MyTask.canRetry(java.lang.RuntimeException) execute with datasource is master canRetry:90418597 2020-08-29 23:50:53,620 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection ## actual delay:3734 doExecute:307531674 2020-08-29 23:50:53,621 INFO [main] com.dianwoba.common.datasource.DataSourceAspect:invoke:32 restore database connection Exception in thread "main" java.lang.RuntimeException: my test

注意:1.2.5之后表达式的预发有所改变,详情可以参考官方文档:https://github.com/spring-projects/spring-retry

相关文章

HashMap是Java中最常用的集合类框架,也是Java语言中非常典型...
在EffectiveJava中的第 36条中建议 用 EnumSet 替代位字段,...
介绍 注解是JDK1.5版本开始引入的一个特性,用于对代码进行说...
介绍 LinkedList同时实现了List接口和Deque接口,也就是说它...
介绍 TreeSet和TreeMap在Java里有着相同的实现,前者仅仅是对...
HashMap为什么线程不安全 put的不安全 由于多线程对HashMap进...