背景
当业务执行失败之后,进行重试是一个非常常见的场景,那么如何在业务代码中优雅的实现重试机制呢?
设计
我们的目标是实现一个优雅的重试机制,那么先来看下怎么样才算是优雅
无侵入:这个好理解,不改动当前的业务逻辑,对于需要重试的地方,可以很简单的实现
可配置:包括重试次数,重试的间隔时间,是否使用异步方式等
通用性:最好是无改动(或者很小改动)的支持绝大部分的场景,拿过来直接可用
针对上面的几点,分别看下右什么好的解决方案
几种解决思路
要想做到无侵入或者很小的改动,一般来将比较好的方式就是切面或者消息总线模式;可配置和通用性则比较清晰了,基本上开始做就表示这两点都是基础要求了,唯一的要求就是不要硬编码,不要写死,基本上就能达到这个基础要求,当然要优秀的话,要做的事情并不少
切面方式
这个思路比较清晰,在需要添加重试的方法上添加一个用于重试的自定义注解,然后在切面中实现重试的逻辑,主要的配置参数则根据注解中的选项来初始化
优点:
真正的无侵入
缺点:
某些方法无法被切面拦截的场景无法覆盖(如spring-aop无法切私有方法,final方法)
直接使用aspecj则有些小复杂;如果用spring-aop,则只能切被spring容器管理的bean
消息总线方式
这个也比较容易理解,在需要重试的方法中,发送一个消息,并将业务逻辑作为回调方法传入;由一个订阅了重试消息的consumer来执行重试的业务逻辑
优点:
重试机制不受任何限制,即在任何地方你都可以使用
利用EventBus框架,可以非常容易把框架搭起来
缺点:
业务侵入,需要在重试的业务处,主动发起一条重试消息
调试理解复杂(消息总线方式的最大优点和缺点,就是过于灵活了,你可能都不知道什么地方处理这个消息,特别是新的童鞋来维护这段代码时)
如果要获取返回结果,不太好处理, 上下文参数不好处理
模板方式
把这个单独捞出来,主要是某些时候我就一两个地方要用到重试,简单的实现下就好了,也没有必用用到上面这么重的方式;而且我希望可以针对代码快进行重试
这个的设计还是非常简单的,基本上代码都可以直接贴出来,一目了然:
public abstract class RetryTemplate {
private static final int DEFAULT_RETRY_TIME = 1;
private int retryTime = DEFAULT_RETRY_TIME;
// 重试的睡眠时间
private int sleepTime = 0;
public int getSleepTime() {
return sleepTime;
}
public RetryTemplate setSleepTime(int sleepTime) {
if(sleepTime < 0) {
throw new IllegalArgumentException("sleepTime should equal or bigger than 0");