重试: 这个故障是暂时的,不是永久的,我们才会去重试。
设计重试时,一定要结合业务场景,定义出什么情况下需要重试。(比如:调用超时)
一些错误最好不要重试,如:
- 没有权限
- 非法请求数据
- 数据格式不正确
- 要确定什么样的错误才需要重试
- 重试的时间和重试的次数
- 超时重试次数,或是一段时间,那么重试就没有意义了。此后对于新来的请求,就没有必要再进行重试了。此时新来的请求直接返回错误即可。
- 重试要考虑被调用方是否支持幂等设计
- 可以借助框架,如 Spring Annotation,不用侵入业务代码
- 对于有事务相关的操作。我们可能会希望能重试成功,而不至于走业务补偿那样的复杂的回退流程。对此,我们可能需要一个比较长的时间来做重试,但是我们需要保存住请求的上下文,这可能对程序的运行有比较大的开销,因此,有一些设计会先把这样的上下文暂存在本机或是数据库中,然后腾出资源来去做别的事,过一会再回来把之前的请求从存储中捞出来重试。
要有最大值,经过一段时间的重试后,没有必要再重试了。重试可能加重网络负担。
Exponential Backoff (指数策略)
如:第一次等 200ms;第二次等400ms;第三次等800ms
https://github.com/spring-projects/spring-retry
以AOP的方式通过Annotation的方式使用。
@Service
public interface MyService {
@Retryable(
value = { SQLException.class },
maxAttempts = 2,
backoff = @Backoff(delay = 5000))
void retryService(String sql) throws SQLException;
...
}
配置 @Retryable 注解,只对 SQLException 的异常进行重试,重试两次,每次延时 5000ms。相关的细节可以看相应的文档。我在这里,只想让你看一下 Spring 有哪些重试的策略。
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
template.setRetryPolicy(policy);
Foo result = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// Do stuff that might fail, e.g. webservice operation
return result;
}
});
-
NeverRetryPolicy:只允许调用 RetryCallback 一次,不允许重试。
-
AlwaysRetryPolicy:允许无限重试,直到成功,此方式逻辑不当会导致死循环。
-
SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为 3 次,RetryTemplate 默认使用的策略。
-
TimeoutRetryPolicy:超时时间重试策略,默认超时时间为 1 秒,在指定的超时时间内允许重试。
-
CircuitBreakerRetryPolicy:有熔断功能的重试策略,需设置 3 个参数 openTimeout、resetTimeout 和 delegate;关于熔断,会在后面描述。
-
CompositeRetryPolicy:组合重试策略。有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即不可以。但不管哪种组合方式,组合中的每一策略都会执行。