spring-retry
Spring Retry 为 Spring 应用程序提供了声明性重试支持。 它用于Spring批处理、Spring集成、Apache Hadoop(等等)的Spring。
在分布式系统中,为了保证数据分布式事务的强一致性,大家在调用RPC接口或者发送MQ时,针对可能会出现网络抖动请求超时情况采取一下重试操作。 大家用的最多的重试方式就是MQ了,但是如果你的项目中没有引入MQ,那就不方便了。
还有一种方式,是开发者自己编写重试机制,但是大多不够优雅。
注解式使用
- RemoteService.java
重试条件:遇到 RuntimeException
重试次数:3
重试策略:重试的时候等待 5S, 后面时间依次变为原来的 2 倍数。
熔断机制:全部重试失败,则调用 recover() 方法。
@Service public class RemoteService { private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class); /** * 调用方法 */ @Retryable(value = RuntimeException.class, maxAttempts = 3, backoff = @Backoff(delay = 5000L, multiplier = 2)) public void call() { LOGGER.info("Call something..."); throw new RuntimeException("RPC调用异常"); } /** * recover 机制 * @param e 异常 */ @Recover public void recover(RuntimeException e) { LOGGER.info("Start do recover things...."); LOGGER.warn("We meet ex: ", e); } }
- 测试
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) public class RemoteServiceTest { @Autowired private RemoteService remoteService; @Test public void test() { remoteService.call(); } }
- 日志
2018-08-08 16:03:26.409 INFO 1433 --- [ main] c.g.h.r.spring.service.RemoteService : Call something... 2018-08-08 16:03:31.414 INFO 1433 --- [ main] c.g.h.r.spring.service.RemoteService : Call something... 2018-08-08 16:03:41.416 INFO 1433 --- [ main] c.g.h.r.spring.service.RemoteService : Call something... 2018-08-08 16:03:41.418 INFO 1433 --- [ main] c.g.h.r.spring.service.RemoteService : Start do recover things.... 2018-08-08 16:03:41.425 WARN 1433 --- [ main] c.g.h.r.spring.service.RemoteService : We meet ex: java.lang.RuntimeException: RPC调用异常 at com.github.houbb.retry.spring.service.RemoteService.call(RemoteService.java:38) ~[classes/:na] ...
三次调用的时间点:
2018-08-08 16:03:26.409 2018-08-08 16:03:31.414 2018-08-08 16:03:41.416
缺陷
spring-retry 工具虽能优雅实现重试,但是存在两个不友好设计:
一个是重试实体限定为 Throwable 子类,说明重试针对的是可捕捉的功能异常为设计前提的,但是我们希望依赖某个数据对象实体作为重试实体,
但 sping-retry框架必须强制转换为Throwable子类。
另一个就是重试根源的断言对象使用的是 doWithRetry 的 Exception 异常实例,不符合正常内部断言的返回设计。
Spring Retry 提倡以注解的方式对方法进行重试,重试逻辑是同步执行的,重试的“失败”针对的是Throwable,
如果你要以返回值的某个状态来判定是否需要重试,可能只能通过自己判断返回值然后显式抛出异常了。
@Recover 注解在使用时无法指定方法,如果一个类中多个重试方法,就会很麻烦。
注解介绍
@EnableRetry
表示是否开始重试。
序号 |
属性 |
类型 |
默认值 |
说明 |
1 |
proxyTargetClass |
boolean |
false |
指示是否要创建基于子类的(CGLIB)代理,而不是创建标准的基于Java接口的代理。 |
@Retryable
标注此注解的方法在发生异常时会进行重试
序号 |
属性 |
类型 |
默认值 |
说明 |
1 |
interceptor |
String |
"" |
将 interceptor 的 bean 名称应用到 retryable() |
2 |
value |
Class[] |
{} |
可重试的异常类型。 |
3 |
label |
String |
"" |
统计报告的唯一标签。如果没有提供,调用者可以选择忽略它,或者提供默认值。 |
4 |
maxAttempts |
int |
3 |
尝试的最大次数(包括第一次失败),默认为3次。 |
5 |
backoff |
@Backoff |
@Backoff() |
指定用于重试此操作的backoff属性。默认为空 |
@Backoff
序号 |
属性 |
类型 |
默认值 |
说明 |
|||||
1 |
delay |
long |
0 |
如果不设置则默认使用 1000 milliseconds |
重试等待 |
||||
2 |
maxDelay |
long |
0 |
最大重试等待时间 |
|||||
3 |
multiplier |
long |
0 |
用于计算下一个延迟延迟的乘数(大于0生效) |
|||||
4 |
random |
boolean |
false |
随机重试等待时间 |
@Recover
用于恢复处理程序的方法调用的注释。一个合适的复苏handler有一个类型为可投掷(或可投掷的子类型)的第一个参数br/>和返回与`@Retryable`方法相同的类型的值。
方法式使用
注解式只是让我们使用更加便捷,但是如果要更高的灵活性。可以使用各种提供的方法。
- SimpleDemo.java
public class SimpleDemo { private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDemo.class); public static void main(String[] args) throws Exception { RetryTemplate template = new RetryTemplate(); // 策略 SimpleRetryPolicy policy = new SimpleRetryPolicy(); policy.setMaxAttempts(2); template.setRetryPolicy(policy); String result = template.execute( new RetryCallback<String, Exception>() { @Override public String doWithRetry(RetryContext arg0) { throw new NullPointerException(); } } , new RecoveryCallback<String>() { @Override public String recover(RetryContext context) { return "recovery callback"; } } ); LOGGER.info("result: {}", result); } }
- 执行日志
16:30:52.578 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry: count=0 16:30:52.591 [main] DEBUG org.springframework.retry.support.RetryTemplate - Checking for rethrow: count=1 16:30:52.591 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry: count=1 16:30:52.591 [main] DEBUG org.springframework.retry.support.RetryTemplate - Checking for rethrow: count=2 16:30:52.591 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry failed last attempt: count=2 16:30:52.592 [main] INFO com.github.houbb.retry.spring.commonway.SimpleDemo - result: recovery callback
spring-retry 结构
概览
- RetryCallback: 封装你需要重试的业务逻辑(上文中的doSth)
- RecoverCallback:封装在多次重试都失败后你需要执行的业务逻辑(上文中的doSthWhenStillFail)
- RetryContext: 重试语境下的上下文,可用于在多次Retry或者Retry 和Recover之间传递参数或状态(在多次doSth或者doSth与doSthWhenStillFail之间传递参数)
- RetryOperations : 定义了“重试”的基本框架(模板),要求传入RetryCallback,可选传入RecoveryCallback;
- RetryListener:典型的“监听者”,在重试的不同阶段通知“监听者”(例如doSth,wait等阶段时通知)
- RetryPolicy : 重试的策略或条件,可以简单的进行多次重试,可以是指定超时时间进行重试(上文中的someCondition)
- BackOffPolicy: 重试的回退策略,在业务逻辑执行发生异常时。如果需要重试,我们可能需要等一段时间(可能服务器过于繁忙,如果一直不间隔重试可能拖垮服务器),
当然这段时间可以是 0,也可以是固定的,可以是随机的(参见tcp的拥塞控制算法中的回退策略)。回退策略在上文中体现为wait();
- RetryTemplate: RetryOperations的具体实现,组合了RetryListener[],BackOffPolicy,RetryPolicy。
重试策略
- NeverRetryPolicy:只允许调用RetryCallback一次,不允许重试
- AlwaysRetryPolicy:允许无限重试,直到成功,此方式逻辑不当会导致死循环
- SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
- TimeoutRetryPolicy:超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
- ExceptionClassifierRetryPolicy:设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
- CircuitBreakerRetryPolicy:有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate
- CompositeRetryPolicy:组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,
悲观组合重试策略是指只要有一个策略不允许重试即可以,但不管哪种组合方式,组合中的每一个策略都会执行
重试回退策略
重试回退策略,指的是每次重试是立即重试还是等待一段时间后重试。
默认情况下是立即重试,如果需要配置等待一段时间后重试则需要指定回退策略BackoffRetryPolicy。
- NoBackOffPolicy:无退避算法策略,每次重试时立即重试
- FixedBackOffPolicy:固定时间的退避策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒
- UniformRandomBackOffPolicy:随机时间退避策略,需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该策略在[minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒
- ExponentialBackOffPolicy:指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier
- ExponentialRandomBackOffPolicy:随机指数退避策略,引入随机乘数可以实现随机乘数回退
来源:oschina
链接:https://my.oschina.net/u/1866807/blog/2978325