关于重试
开发中经常要调用其他项目提供的Api,这些Api可能是同公司其他团队或其他公司提供的,所以避免不了会出现网络抖动调用失败的情况,这种情况下往往重试一次就正常了。对于add或update操作,如果是非幂等性操作,要谨慎使用,重试可能会造成业务异常。
可以使用apache HttpClient 或自己实现,spring 也提供了重试机制 Spring Retry。
GitHub地址: Spring-Retry
有两种使用方式,基于编码方式和基于申明式,下面是常用类图:
常用类图结构
补偿/回退 策略重试策略
重试上下文缓存(强引用 与 软引用 缓存方式)
重试监听者(实现不同阶段的通知功能)
引入依赖
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
编码方式
RetryTemplate配置
@Bean(name = "retryTemplate") public RetryTemplate getRetryTemplate() { RetryTemplate template = new RetryTemplate(); FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); //设置2s重试一次 backOffPolicy.setBackOffPeriod(2000); template.setBackOffPolicy(backOffPolicy); SoftReferenceMapRetryContextCache contextCache = new SoftReferenceMapRetryContextCache(); template.setRetryContextCache(contextCache); SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); template.setRetryPolicy(retryPolicy); template.setThrowLastExceptionOnExhausted(true); return template; }
不指定 RecoveryCallback
@Autowired RetryTemplate retryTemplate;
@RequestMapping(value = "/test", method = { RequestMethod.GET }) public void test() { Boolean execute = retryTemplate.execute(ctx -> testMethod("arg-1", 12)); System.out.println("result : " + execute); } public Boolean testMethod(String arg1, Integer arg2) throws RuntimeException { logger.info("调用 testMethod 方法:arg1:" + arg1 + ",arg2:" + arg2); throw new RuntimeException("出错啦!"); }
执行结果:
2019-08-03 18:23:19.557 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:65] 调用 testMethod 方法:arg1:arg-1,arg2:12 2019-08-03 18:23:21.563 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:65] 调用 testMethod 方法:arg1:arg-1,arg2:12 2019-08-03 18:23:23.563 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:65] 调用 testMethod 方法:arg1:arg-1,arg2:12 2019-08-03 18:23:23.567 [ERROR][http-nio-8097-exec-1]:com.demo.CommonExceptionAdvice [processUnauthenticatedException:45] java.lang.RuntimeException: 出错啦!
指定 RecoveryCallback
@RequestMapping(value = "/test", method = { RequestMethod.GET }) public void test() { Boolean execute = retryTemplate.execute(ctx -> testMethod("arg-1", 12), ctx -> callBack("arg-1", 12)); System.out.println("result : " + execute); } public Boolean testMethod(String arg1, Integer arg2) throws RuntimeException { logger.info("调用 testMethod 方法:arg1:" + arg1 + ",arg2:" + arg2); throw new RuntimeException("出错啦!"); } public Boolean callBack(String arg1, Integer arg2) throws RuntimeException { logger.info("调用 callBack 方法:arg1:" + arg1 + ",arg2:" + arg2); return false; }
返回结果:
2019-08-03 18:39:23.777 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:63] 调用 testMethod 方法:arg1:arg-1,arg2:12 2019-08-03 18:39:25.781 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:63] 调用 testMethod 方法:arg1:arg-1,arg2:12 2019-08-03 18:39:27.781 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [testMethod:63] 调用 testMethod 方法:arg1:arg-1,arg2:12 2019-08-03 18:39:27.781 [INFO][http-nio-8097-exec-1]:c.demo.controller.DemoController [callBack:68] 调用 callBack 方法:arg1:arg-1,arg2:12 result : false
注解申明
@Retryable注解
注解参数:
value:指定发生的异常进行重试
include:和value一样,默认空,当exclude也为空时,所有异常都重试
exclude:指定异常不重试,默认空,当include也为空时,所有异常都重试
maxAttemps:重试次数,默认3
backoff:重试补偿机制,默认没有
/** * Exception types that are retryable. Synonym for includes(). Defaults to empty (and * if excludes is also empty all exceptions are retried). * @return exception types to retry */ Class<? extends Throwable>[] value() default {}; /** * Exception types that are retryable. Defaults to empty (and if excludes is also * empty all exceptions are retried). * @return exception types to retry */ Class<? extends Throwable>[] include() default {}; /** * Exception types that are not retryable. Defaults to empty (and if includes is also * empty all exceptions are retried). * @return exception types to retry */ Class<? extends Throwable>[] exclude() default {}; /** * @return the maximum number of attempts (including the first failure), defaults to 3 */ int maxAttempts() default 3; /** * Specify the backoff properties for retrying this operation. The default is no * backoff, but it can be a good idea to pause between attempts (even at the cost of * blocking a thread). * @return a backoff specification */ Backoff backoff() default @Backoff();View Code
@Backoff
value:和delay一样,在指数级情况下用作初始值
maxDelay:两次重试之间的最大时间如果小于delay,则忽略
multiplier:如果为正数,用来作为生成下一次补偿的延迟时间乘数,默认值0表示忽略(eg:2, 第一次 delay * 2 第二次 delay * 2 * 2)
/** * Synonym for {@link #delay()}. * * @return the delay in milliseconds (default 1000) */ long value() default 1000; /** * A canonical backoff period. Used as an initial value in the exponential case, and * as a minimum value in the uniform case. * @return the initial or canonical backoff period in milliseconds (default 1000) */ long delay() default 0; /** * The maximimum wait (in milliseconds) between retries. If less than the * {@link #delay()} then ignored. * * @return the maximum delay between retries (default 0 = ignored) */ long maxDelay() default 0; /** * If positive, then used as a multiplier for generating the next delay for backoff. * * @return a multiplier to use to calculate the next backoff delay (default 0 = * ignored) */ double multiplier() default 0; /** * An expression evaluating to the canonical backoff period. Used as an initial value * in the exponential case, and as a minimum value in the uniform case. * Overrides {@link #delay()}. * @return the initial or canonical backoff period in milliseconds. * @since 1.2 */ String delayExpression() default ""; /** * An expression evaluating to the maximimum wait (in milliseconds) between retries. * If less than the {@link #delay()} then ignored. * Overrides {@link #maxDelay()} * * @return the maximum delay between retries (default 0 = ignored) * @since 1.2 */ String maxDelayExpression() default ""; /** * Evaluates to a vaule used as a multiplier for generating the next delay for backoff. * Overrides {@link #multiplier()}. * * @return a multiplier expression to use to calculate the next backoff delay (default 0 = * ignored) * @since 1.2 */ String multiplierExpression() default ""; /** * In the exponential case ({@link #multiplier()} > 0) set this to true to have the * backoff delays randomized, so that the maximum delay is multiplier times the * previous delay and the distribution is uniform between the two values. * * @return the flag to signal randomization is required (default false) */ boolean random() default false;View Code
示例
在主类上加上@EnableRetry
注解,表示启用重试机制。
@EnableRetry @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
指定重试异常 和 重试指数
public Boolean testMethod(String arg1, Integer arg2) throws RuntimeException { logger.info("调用 testMethod 方法:arg1:" + arg1 + ",arg2:" + arg2); throw new RuntimeException("出错啦!"); } @RequestMapping(value = "/testAn/{arg1}/{arg2}", method = { RequestMethod.GET }) @Retryable(value = {Exception.class, RuntimeException.class}, backoff = @Backoff(multiplier = 1.5)) public Boolean testExe(@PathVariable("arg1") String arg1, @PathVariable("arg2") Integer arg2) { Boolean result = testMethod(arg1, arg2); System.out.println("result : " + result); return result; } @Recover public Boolean backOffMethod(RuntimeException ex, String arg1, Integer arg2) { logger.info("调用 backOffMethod 方法:arg1:" + arg1 + ",arg2:" + arg2); return false; }
重试结果
2019-08-07 15:24:31.790 [INFO][http-nio-8097-exec-8]:c.ppdai.koocapp.web.controller.SysCheckController [testMethod:51] [eca27f812e304ec0964ddff868c1c98c] 调用 testMethod 方法:arg1:test-annotation,arg2:12 2019-08-07 15:24:32.791 [INFO][http-nio-8097-exec-8]:c.ppdai.koocapp.web.controller.SysCheckController [testMethod:51] [eca27f812e304ec0964ddff868c1c98c] 调用 testMethod 方法:arg1:test-annotation,arg2:12 2019-08-07 15:24:34.291 [INFO][http-nio-8097-exec-8]:c.ppdai.koocapp.web.controller.SysCheckController [testMethod:51] [eca27f812e304ec0964ddff868c1c98c] 调用 testMethod 方法:arg1:test-annotation,arg2:12 2019-08-07 15:24:34.292 [INFO][http-nio-8097-exec-8]:c.ppdai.koocapp.web.controller.SysCheckController [backOffMethod:71] [eca27f812e304ec0964ddff868c1c98c] 调用 backOffMethod 方法:arg1:test-annotation,arg2:12