前言
在分布式场景中,调用第三方接口会因为网络延迟、异常导致调用的服务出错,重试几次可能就会调用成功,是提高结果正确性的一种有效手段。重试机制最简单呢理解为try-catch-redo
模式,但是优雅的重试也是有要求的,至少应该满足如下要求:
- 无侵入:不改动当前的业务逻辑,对于需要重试的地方,可以很简单的实现
- 可配置:包括重试次数,重试的间隔时间,是否使用异步方式等
- 通用性:最好是无改动(或者很小改动)的支持绝大部分的场景
重试固然重要,但不是什么场景下都适合重试的,并且重试在生产环境中需要慎用。对于重试是有场景限制的,比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。远程调用超时、网络突然中断可以重试。在微服务治理框架中,通常都有自己的重试与超时配置,Ribbon自然也有重试的能力。
RetryHandler重试处理器
重试,是类似于Ribbon这种组件里特别重要的概念,因此此接口特别的重要。它负责对执行时若发生异常时的一个处理接口:重试or让异常继续抛出。
public interface RetryHandler {
public static final RetryHandler DEFAULT = new DefaultLoadBalancerRetryHandler();
// 该异常是否可处理(可重试)
// sameServer:true表示在同一台机器上重试。否则去其它机器重试
public boolean isRetriableException(Throwable e, boolean sameServer);
// 是否是Circuit熔断类型异常。比如java.net.ConnectException就属于这种故障
// 这种异常类型一般属于比较严重的,发生的次数多了就会把它熔断(下次不会再找它了)
public boolean isCircuitTrippingException(Throwable e);
// 要在一台服务器上执行的最大重试次数
public int getMaxRetriesOnSameServer();
// 要重试的最大不同服务器数。2表示最多去2台不同的服务器身上重试
public int getMaxRetriesOnNextServer();
}
我们先来看 DefaultLoadBalancerRetryHandler
再来看看 RequestSpecificRetryHandler
public class RequestSpecificRetryHandler implements RetryHandler {
// fallback默认使用的是RetryHandler.DEFAULT
// 有点代理的意思
private final RetryHandler fallback;
private int retrySameServer = -1;
private int retryNextServer = -1;
// 只有是连接异常,也就是SocketException或者其子类异常才执行重试
private final boolean okToRetryOnConnectErrors;
// 若是true:只要异常了,任何错都执行重试
private final boolean okToRetryOnAllErrors;
protected List<Class<? extends Throwable>> connectionRelated = Lists.newArrayList(SocketException.class);
public boolean isConnectionException(Throwable e) {
return Utils.isPresentAsCause(e, connectionRelated);
}
// 构造器为属性赋值。requestConfig可以是单独的,若没指定就使用默认全局的
public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors, RetryHandler baseRetryHandler, @Nullable IClientConfig requestConfig) {
Preconditions.checkNotNull(baseRetryHandler);
this.okToRetryOnConnectErrors = okToRetryOnConnectErrors;
this.okToRetryOnAllErrors = okToRetryOnAllErrors;
this.fallback = baseRetryHandler;
if (requestConfig != null) {
if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetries)) {
retrySameServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetries);
}
if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetriesNextServer)) {
retryNextServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetriesNextServer);
}
}
}
}
@Override
public boolean isRetriableException(Throwable e, boolean sameServer) {
// 若强制开启所有错误都重试,那就没啥好说的
// 此参数默认是false,只能通过构造器来指定其值
if (okToRetryOnAllErrors) {
return true;
}
// ClientException属于执行过程中会抛出的异常类型,所以需要加以判断
else if (e instanceof ClientException) {
ClientException ce = (ClientException) e;
// 若是服务端异常,那就同一台Server上不用重试了,没要求是同一台Server才允许其重试
if (ce.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
return !sameServer;
// 若不是服务端异常类型,那就换台Server都不用重试了
} else {
return false;
}
}
// 若不是ClientException,那就看看异常是否是Socket的链接异常喽
// okToRetryOnConnectErrors的值也是由构造的时候指定的
// 没有默认值....
else {
return okToRetryOnConnectErrors && isConnectionException(e);
}
}