sisyphus
github:https://github.com/houbb/sisyphus
一、工具类方式使用
1、POM
<!-- retry --> <dependency> <groupId>com.github.houbb</groupId> <artifactId>sisyphus-core</artifactId> <version>0.0.8</version> </dependency> <dependency> <groupId>com.github.houbb</groupId> <artifactId>sisyphus-annotation</artifactId> <version>0.0.8</version> </dependency>
2、SisyphusUtil:
package com.xxx.mitv.common.util; import com.github.houbb.sisyphus.core.core.RetryWaiter; import com.github.houbb.sisyphus.core.core.Retryer; import com.github.houbb.sisyphus.core.support.condition.RetryConditions; import com.github.houbb.sisyphus.core.support.listen.RetryListens; import com.github.houbb.sisyphus.core.support.recover.Recovers; import com.github.houbb.sisyphus.core.support.wait.ExponentialRetryWait; import com.github.houbb.sisyphus.core.support.wait.NoRetryWait; import java.util.concurrent.Callable; /** * 重试工具类
*/ public class SisyphusUtil { /** * 默认配置 * * @param callable 待重试执行方法 */ public static void defaultAttempt(final Callable<String> callable) { Retryer.<String>newInstance() //重试触发的条件,可以指定多个条件,默认为抛出异常。 .condition(RetryConditions.hasExceptionCause()) //重试等待的策略,可以指定多个,默认为不做任何等待。 .retryWaitContext(RetryWaiter.<String>retryWait(NoRetryWait.class).context()) //指定最大重试次数,包括第一次执行,默认3次 .maxAttempt(3) //指定重试的监听实现,默认为不做监听。 .listen(RetryListens.noListen()) //当重试完成之后,依然满足重试条件,则可以指定恢复的策略。默认不做恢复。 .recover(Recovers.noRecover()) //待重试执行的方法。 .callable(callable) //触发重试执行。 .retryCall(); } /** * 根据返回值等于期望值定制重试规则 * * @param condition 触发条件 * @param maxAttempt 最大重试次数 * @param initValue 第一次重试时间间隔 单位:毫秒。初始建议设置60000 * @param factor 递增因子 小于1越来越快,大于1越来越慢,等于1保持不变。 大部分场景设置2.0可满足需求 * @param callable 待重试执行方法 */ public static void attemptForEqualsResult(final String condition, final int maxAttempt, final int initValue, final double factor, final Callable<String> callable) { Retryer.<String>newInstance() .condition(RetryConditions.isEqualsResult(condition)) //递增策略 .retryWaitContext(RetryWaiter.<String>retryWait(ExponentialRetryWait.class).value(initValue).factor(factor).context()) .maxAttempt(maxAttempt) .listen(RetryListens.noListen()) .recover(Recovers.noRecover()) .callable(callable) .retryCall(); } /** * 根据返回值不等于期望值定制重试规则 * * @param condition 触发条件 * @param maxAttempt 最大重试次数 * @param initValue 第一次重试时间间隔 单位:毫秒。初始建议设置60000 * @param factor 递增因子 小于1越来越快,大于1越来越慢,等于1保持不变。 大部分场景设置2.0可满足需求 * @param callable 待重试执行方法 */ public static void attemptForNotEqualsResult(final String condition, final int maxAttempt, final int initValue, final double factor, final Callable<String> callable) { Retryer.<String>newInstance() .condition(RetryConditions.isNotEqualsResult(condition)) //递增策略 .retryWaitContext(RetryWaiter.<String>retryWait(ExponentialRetryWait.class).value(initValue).factor(factor).context()) .maxAttempt(maxAttempt) .listen(RetryListens.noListen()) .recover(Recovers.noRecover()) .callable(callable) .retryCall(); } /** * 根据是否存在异常定制重试规则 * * @param maxAttempt 最大重试次数 * @param initValue 第一次重试时间间隔 单位:毫秒。初始建议设置60000 * @param factor 递增因子 小于1越来越快,大于1越来越慢,等于1保持不变。 大部分场景设置2.0可满足需求 * @param callable 待重试执行方法 */ public static void attemptForException(final int maxAttempt, final int initValue, final double factor, final Callable<String> callable) { Retryer.<String>newInstance() .condition(RetryConditions.hasExceptionCause()) .retryWaitContext(RetryWaiter.<String>retryWait(ExponentialRetryWait.class).value(initValue).factor(factor).context()) .maxAttempt(maxAttempt) .listen(RetryListens.noListen()) .recover(Recovers.noRecover()) .callable(callable) .retryCall(); } }
3、应用
工具类方式:
//重试3次 SisyphusUtil.attemptForNotEqualsResult( "true", 3, 60000, 2.0, new Callable<String>() { @Override public String call() throws Exception { return autoRenewService.wechatAdvanceNotify(info) == true ? "true" : "false"; } });
注解方式:与Spring整合
@Retry :
/** * 重试注解 * 1. 实际需要,只允许放在方法上。 * 2. 如果放在接口上,是否所有的子类都生效?为了简单明确,不提供这种实现。 * 3. 保持注解和接口的一致性。{@link com.github.houbb.sisyphus.api.core.Retry} 接口 * @since 0.0.3 */ @Documented @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @RetryAble(DefaultRetryAbleHandler.class) public @interface Retry { /** * 重试类实现 * @return 重试 * @since 0.0.5 */ Class<? extends com.github.houbb.sisyphus.api.core.Retry> retry() default DefaultRetry.class; /** * 最大尝试次数 * 1. 包含方法第一次正常执行的次数 * @return 次数 */ int maxAttempt() default 3; /** * 重试触发的场景 * @return 重试触发的场景 */ Class<? extends RetryCondition> condition() default ExceptionCauseRetryCondition.class; /** * 监听器 * 1. 默认不进行监听 * @return 监听器 */ Class<? extends RetryListen> listen() default NoRetryListen.class; /** * 恢复操作 * 1. 默认不进行任何恢复操作 * @return 恢复操作对应的类 */ Class<? extends Recover> recover() default NoRecover.class; /** * 等待策略 * 1. 支持指定多个,如果不指定,则不进行任何等待, * @return 等待策略 */ RetryWait[] waits() default {}; }
二、与Spring整合:
1、POM
<dependency> <groupId>com.github.houbb</groupId> <artifactId>sisyphus-spring</artifactId> <version>0.0.8</version> </dependency>
会默认引入 spring 以及 AOP 相关 jar
2、开启重试
@EnableRetry
/** * 启用重试注解 * @since 0.0.4 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(RetryAopConfig.class) @EnableAspectJAutoProxy public @interface EnableRetry { }
3、使用 @Retry 标识方法需要进行重试。
如:
@Retry @Override public void test() { LOGGER.info("test"); int i=1/0; }
spring retry
https://github.com/spring-projects/spring-retry#javaConfigForRetryProxies
翻译的中文文档:https://segmentfault.com/a/1190000019932970
总结好文:https://albenw.github.io/posts/69a9647f/
spring retry是通过 AOP 实现的
1、依赖
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.2.4.RELEASE</version> </dependency>
如果使用 @Retryable 注解还需额外添加 aop和aspectj的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency>
2、在启动类或配置类加上 @EnableRetry 注解,表示启用重试机制
3、举例:
@Configuration @EnableRetry public class Application { @Bean public Service service() { return new Service(); } } @Service class Service { @Retryable(RemoteAccessException.class) public void service() { // ... do something } @Recover public void recover(RemoteAccessException e) { // ... panic } }
在此例中,当调用service()方法出现 RemoteAccessException 异常时,默认重试 3次(包含第一次的失败),如果仍失败,则会调用 recover 方法。
@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 {}; /** * 不重试的异常类型,默认为空(并且如果exclude也是空的话,所有的异常都会重试) */ Class<? extends Throwable>[] exclude() default {}; /** * A unique label for statistics reporting. If not provided the caller may choose to * ignore it, or provide a default. * * @return the label for the statistics */ String label() default ""; /** * 标识重试是有状态的。如果异常在重试的时候重新抛出, * Flag to say that the retry is stateful: i.e. exceptions are re-thrown, but the * retry policy is applied with the same policy to subsequent invocations with the * same arguments. If false then retryable exceptions are not re-thrown. * @return true if retry is stateful, default false */ boolean stateful() default false; /** * 尝试的最大次数(包含第一次失败),默认为3 */ int maxAttempts() default 3; /** * 返回一个求尝试最大次数值的表达式(包含第一次失败),默认为3 * 重写 {@link #maxAttempts()}。 */ String maxAttemptsExpression() default ""; /** * 退避策略,指怎么去做下一次的重试,在这里其实就是两次重试之间的间隔 */ Backoff backoff() default @Backoff(); /** * 在SimpleRetryPolicy.canRetry()返回true之后执行一个计算表达式,可用来有条件的取消重试 * 只在抛出异常后调用,求值的root对象为上一次的异常,可以引用上下文中的其他beans */ String exceptionExpression() default ""; }
@Backoff
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(RetryConfiguration.class) @Documented public @interface Backoff { /** * 与 delay() 属性同义 * 返回延迟多少毫秒后重试(默认为1000毫秒) */ long value() default 1000; /** * 一个标准的再重试周期,默认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; /** * 标准再重试周期求值表达式。在指数情况下用作初始值,始终如一的情况下用作最小值。 * Overrides {@link #delay()}. * @return the initial or canonical backoff period in milliseconds. * @since 1.2 */ String delayExpression() default ""; /** * 在重试之间最大等待(毫秒)数的求值表达式。 * * 如果小于 {@link #delay()} 则忽略。 * Overrides {@link #maxDelay()} * @return the maximum delay between retries (default 0 = ignored) * @since 1.2 */ String maxDelayExpression() default ""; /** * 表达式求值作为生成下次再重试延迟的乘数 * Overrides {@link #multiplier()}. * @since 1.2 */ String multiplierExpression() default ""; /** * 在指数情况下 ({@link #multiplier()} > 0) 设置该值为true将使再重试延迟随机化, * 使最大延迟为先前延迟的乘数倍数,并使这两个延迟值之间分布均匀。 * 默认为false */ boolean random() default false; }
实例:
/** * 第一次延迟1000ms,第二次延迟2*1000ms,第三次延迟2*2*1000ms,之后都是延迟5000ms */ @Retryable(maxAttempts = 6, backoff = @Backoff(delay = 1000, maxDelay = 5000, multiplier = 2)) @Override public void test() { LOGGER.error("spring retry:{}", DateUtil.formatDate(new Date())); throw new RuntimeException("my test"); }
输出:
2020-09-28 14:12:24.536|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=0 2020-09-28 14:12:24.539|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:24 GMT 2020-09-28 14:12:24.539|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 1000 2020-09-28 14:12:25.541|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=1
2020-09-28 14:12:25.541|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=1 2020-09-28 14:12:25.541|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:25 GMT 2020-09-28 14:12:25.541|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 2000 2020-09-28 14:12:27.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=2
2020-09-28 14:12:27.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=2 2020-09-28 14:12:27.542|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:27 GMT 2020-09-28 14:12:27.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 40002020-09-28 14:12:31.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=3
2020-09-28 14:12:31.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=3 2020-09-28 14:12:31.542|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:31 GMT 2020-09-28 14:12:31.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 50002020-09-28 14:12:36.543|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=4
2020-09-28 14:12:36.543|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=4 2020-09-28 14:12:36.543|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:36 GMT 2020-09-28 14:12:36.543|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 50002020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=5
2020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=5 2020-09-28 14:12:41.544|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:41 GMT 2020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=6 2020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:346|Retry failed last attempt: count=6 2020-09-28 14:12:41.546|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|LogAspect#around:59|test方法执行异常:my test java.lang.RuntimeException: my test
Spring Retry缺点
1、其回退策略,默认使用的是Thread.sleep方法,会导致当前的线程被阻塞,因此使用的时候要注意。
2、只能在异常后重试,重试条件单一
END.