本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Feign整合Ribbon的连接超时以及失败重试
超时
微服务架构的系统中,至少要配置一个超时的时间,timeout,一个服务调用另外一个服务,得有个超时的时间。设置超时时间,最多等待1秒,没返回直接就判定请求失败。
重试
OrderService
192.168.1.2:8080
ProductService
192.168.1.2:9091(instance001)
192.168.1.2:9092(instance002)
192.168.1.2:9093(instance003)
订单服务,调用商品服务,商品服务部署3台机器。
订单服务通过负载均衡的算法,调用到了商品服务的instance001实例,结果因为商品服务的instance001实例宕机,请求超时。可以订单服务尝试再次请求商品服务的instance001实例,如果请求依旧失败,那么可以尝试请求商品服务的instance002实例,如果还是不行,可以去尝试商品服务的instance003实例。
超时、重试、隔离、限流、熔断、降级
OpenFeign整合Ribbon,配置超时和重试
# feign service config
## feign eureka-provider-ribbon-feign-api-impl service config
#feign.client.config.eureka-provider-ribbon-feign-api-impl.connect-timeout=5000
#feign.client.config.eureka-provider-ribbon-feign-api-impl.read-timeout=5000
feign.client.config.eureka-provider-ribbon-feign-api-impl.logger-level=full
feign.client.config.eureka-provider-ribbon-feign-api-impl.decode404=false
## feign default service config
#feign.client.config.default.connect-timeout=3000
#feign.client.config.default.read-timeout=3000
feign.client.config.default.logger-level=headers
feign.client.config.default.decode404=true
## feign retry config
spring.cloud.loadbalancer.retry.enabled=true
ribbon.OkToRetryOnAllOperations=true
# 当前实例重试的次数,失败之后更换下个实例
ribbon.MaxAutoRetries=1
# 更换实例的次数
ribbon.MaxAutoRetriesNextServer=3
ribbon.ConnectTimeout=1000
ribbon.ReadTimeout=2000
连接超时【connect-timeout】和读超时【read-timeout】
前提是Feign配置信息不能配置连接超时【connect-timeout】和读超时【read-timeout】。
如果指定Feign的连接超时【connect-timeout】和读超时【read-timeout】配置信息的话会根据指定参数构造Options。如果没有指定的话会构造缺省的HTTP连接参数【Request.Options DEFAULT_OPTIONS(10 * 1000, 60 * 1000)】。
如果是缺省的Options【DEFAULT_OPTIONS】则根据服务名称【clientName】从子容器中获取FeignOptionsClientConfig & IClientConfig。建立HTTP请求连接时,由于Feign没有配置连接超时【connect-timeout】和读超时【read-timeout】,会以Ribbon的连接超时【connect-timeout】和读超时【read-timeout】配置作为缺省值创建HTTP请求连接配置信息。
重试句柄和重试次数
FeignLoadBalancer.getRequestSpecificRetryHandler()方法中,会读取配参数:OkToRetryOnAllOperations、MaxAutoRetries、MaxAutoRetriesNextServer。
如果Options是不是缺省【DEFAULT_OPTIONS】,导致IClientConfig是FeignOptionsClientConfig子类。
构造RequestSpecificRetryHandler对象时,FeignOptionsClientConfig(IClientConfig)对象不包含MaxAutoRetries、MaxAutoRetriesNextServer,从而MaxAutoRetries、MaxAutoRetriesNextServer两个配置信息会不设置。
LoadBalancerCommand.submit()方法中,在执行请求逻辑的时候,读取RetryHandler(RequestSpecificRetryHandler)中配置的参数,会根据请求的情况,是否报错,是否报异常,进行重试的控制
FeignLoadBalancer.execute(),发送实际的http请求的时候,就会传入设置的超时的参数
启用重试
spring.cloud.loadbalancer.retry.enabled控制Retryer组件,默认是NEVER_RETRY。如果启用重试,会使用支持重试的Retryer。
public interface Retryer extends Cloneable {
/**
* Implementation that never retries request. It propagates the RetryableException.
*/
Retryer NEVER_RETRY = new Retryer() {
@Override
public void continueOrPropagate(RetryableException e) {
throw e;
}
@Override
public Retryer clone() {
return this;
}
};
}
Retryer使用
SynchronousMethodHandler.invoke()方法,如果抛RetryableExeception异常,会使用Retryer.continueOrPropagate()方法进行处理(不一定重试)。
RetryHandler使用
LoadBalancerCommand(RequestSpecificRetryHandler & RetryHandler)
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
}
LoadBalancer选择Server之后,回调ServerOperation#call方法,执行FeignLoadBalancer#execute()方法。
private Func2<Integer, Throwable, Boolean> retryPolicy(final int maxRetrys, final boolean same) {
return new Func2<Integer, Throwable, Boolean>() {
@Override
public Boolean call(Integer tryCount, Throwable e) {
if (e instanceof AbortExecutionException) {
return false;
}
if (tryCount > maxRetrys) {
return false;
}
if (e.getCause() != null && e instanceof RuntimeException) {
e = e.getCause();
}
return retryHandler.isRetriableException(e, same);
}
};
}
maxRetrysSame:
maxRetrysNext:
okToRetryOnAllErrors:
@Override
public boolean isRetriableException(Throwable e, boolean sameServer) {
if (okToRetryOnAllErrors) {
return true;
}
else if (e instanceof ClientException) {
ClientException ce = (ClientException) e;
if (ce.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
return !sameServer;
} else {
return false;
}
}
else {
return okToRetryOnConnectErrors && isConnectionException(e);
}
}