Netflix Feign - 连接超时及失败重试

563 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


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);
    }
}