• 微服务中feign远程调用相关的各种超时问题


    1. 引言

    在spring cloud微服中,feign远程调用可能是大家每天都接触到东西,但很多同学却没咋搞清楚这里边的各种超时问题,生产环境可能会蹦出各种奇怪的问题。
    首先说下结论:
    1)只使用feign组件,不使用ribbion组件,其默认feign的连接超时是10s,读超时是60s;
    2) 同时使用了feign和ribbion组件,(1)若没有任何人为配置超时时间,远程调用使用ribbion的默认超时时间,连接超时、读超时都是1秒钟;(2)若同时主动配置了feign 、ribbion的超时时间,则使用配置的feign超时时间;(3)若只主动配置了feign的超时时间,则使用配置的feign超时时间;(4)若只主动配置了ribbon超时时间,则使用配置的ribbion超时时间。

    2. only feign

    feign本身是通过FeignClientFactoryBean 创建出来的,feign的超时配置也在方法org.springframework.cloud.openfeign.FeignClientFactoryBean#configureUsingConfiguration中。此方法properties.isDefaultToProperties()的默认值是true,我们一般也不会去改它,所以一般是执行红方框中的逻辑。(1)先执行全局配置configureUsingConfiguration(context, builder),(2)再执行属性配置类中FeignClientProperties的默认配置, (3)最后执行属性配置类FeignClientProperties中当前feign client特定的配置。这三个步骤的覆盖顺序是后者覆盖前者,也就是越往后优先级越高。默认情况下我们未做任何配置时,FeignClientProperties是空对象,也就是说此时只会执行步骤(1)的属性配置。
    在这里插入图片描述
    全局属性配置方法org.springframework.cloud.openfeign.FeignClientFactoryBean#configureUsingConfiguration回到spring容器中去取超时配置对象Request.Options
    在这里插入图片描述在这里插入图片描述
    自动配置类相应方法为我们配置了一个Request.Options对象(当spring容器中不存在此类型的bean时)
    在这里插入图片描述
    LoadBalancerFeignClient.DEFAULT_OPTIONS的连接超时、读超时分别是10s、60s.

    
    public class LoadBalancerFeignClient implements Client {
    
    	static final Request.Options DEFAULT_OPTIONS = new Request.Options();
    	//.....
    }	
     public static class Options {
    
        private final long connectTimeout;
        private final TimeUnit connectTimeoutUnit;
        private final long readTimeout;
        private final TimeUnit readTimeoutUnit;
        private final boolean followRedirects;
        //.......
         /**
         * Creates the new Options instance using the following defaults:
         * 
      *
    • Connect Timeout: 10 seconds
    • *
    • Read Timeout: 60 seconds
    • *
    • Follow all 3xx redirects
    • *
    */
    public Options() { this(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true); } //....... }

    3. feign+ribbon

    在feign引入ribbon负载均衡时,远程调用的feign client会用LoadBalancerFeignClient.
    LoadBalancerFeignClient.execute方法中有调用getClientConfig(options, clientName);的代码行,而IClientConfig中包含有feign和ribbion超时时间。
    在这里插入图片描述
    getClientConfig方法参数中的options是feign本身的超时配置,
    可以看到当参数options等于DEFAULT_OPTIONS,即没有为feign主动配置超时时间这种情况,这种情况会到容器用去获取超时配置;当options不等于DEFAULT_OPTIONS,即已经为feign主动配置了超时时间这种情况下,我们会使用feign的超时时间。

    IClientConfig getClientConfig(Request.Options options, String clientName) {
    		IClientConfig requestConfig;
    		if (options == DEFAULT_OPTIONS) {
    			requestConfig = this.clientFactory.getClientConfig(clientName);
    		}
    		else {
    			requestConfig = new FeignOptionsClientConfig(options);
    		}
    		return requestConfig;
    	}
    

    这个IClientConfig 在配置类org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration的配置方法ribbonClientConfig执行config.loadProperties(this.name);将配置文件中ribbion的配置信息赋值到config对象后,又执行了config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT); config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);重新将连接超时、读超时重置为1000毫秒,即1秒。

    @SuppressWarnings("deprecation")
    @Configuration(proxyBeanMethods = false)
    @EnableConfigurationProperties
    // Order is important here, last should be the default, first should be optional
    // see
    // https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
    @Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
    		RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
    public class RibbonClientConfiguration {
    
    /**
    	 * Ribbon client default connect timeout.
    	 */
    	public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
    
    	/**
    	 * Ribbon client default read timeout.
    	 */
    	public static final int DEFAULT_READ_TIMEOUT = 1000;
    
    	/**
    	 * Ribbon client default Gzip Payload flag.
    	 */
    	public static final boolean DEFAULT_GZIP_PAYLOAD = true;
    
    	@RibbonClientName
    	private String name = "client";
    @Bean
    	@ConditionalOnMissingBean
    	public IClientConfig ribbonClientConfig() {
    		DefaultClientConfigImpl config = new DefaultClientConfigImpl();
    		config.loadProperties(this.name);
    		config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
    		config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
    		config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
    		return config;
    	}
    	//....
    	}
        
    }
    

    我们在看看DefaultClientConfigImpl的怎么处理属性的。
    可以看出上边RibbonClientConfiguration.ribbonClientConfig方法中设置的超时时间,它们被放在DefaultClientConfigImpl#properties中的,而取配置属性的底层方法都是getProperty(String key),所以在没有为feign配置超时时间时,ribbon的取值时先取当前指定ribbon client的超时时间,若不存在则取全局ribbon的超时时间,若还是不存在,则取DefaultClientConfigImpl#properties中的默认值1秒。

    public class DefaultClientConfigImpl implements IClientConfig {
    
    
        @Override
        public <T> DefaultClientConfigImpl set(IClientConfigKey<T> key, T value) {
            properties.put(key.key(), value);
            return this;
        }
     protected Object getProperty(String key) {
     		//这里enableDynamicProperties一定是true
            if (enableDynamicProperties) {
                String dynamicValue = null;
                DynamicStringProperty dynamicProperty = dynamicProperties.get(key);
                //dynamicProperty是null
                if (dynamicProperty != null) {
                    dynamicValue = dynamicProperty.get();
                }
                if (dynamicValue == null) {
                //到配置文件中取当前client的'clientName.ribbion.ReadTimeout'这种格式的属性
                    dynamicValue = DynamicProperty.getInstance(getConfigKey(key)).getString();
                    if (dynamicValue == null) {
                                //到配置文件中取全局'ribbion.ReadTimeout'这种格式的属性
                        dynamicValue = DynamicProperty.getInstance(getDefaultPropName(key)).getString();
                    }
                }
                if (dynamicValue != null) {
                    return dynamicValue;
                }
            }
            //DefaultClientConfigImpl类中定义的默认值
            return properties.get(key);
        }
    
    }
    

    LoadBalancerFeignClient#execute 方法中的lbClient(clientName)创建了一个负载均衡器RetryableFeignLoadBalancer,且此负载均衡期中的config就是上边RibbonClientConfiguration#ribbonClientConfig配置方法中所对应的IClientConfig.

    private FeignLoadBalancer lbClient(String clientName) {
    		return this.lbClientFactory.create(clientName);
    	}
    public FeignLoadBalancer create(String clientName) {
    		FeignLoadBalancer client = this.cache.get(clientName);
    		if (client != null) {
    			return client;
    		}
    		IClientConfig config = this.factory.getClientConfig(clientName);
    		ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
    		ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
    				ServerIntrospector.class);
    		client = this.loadBalancedRetryFactory != null
    				? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
    						this.loadBalancedRetryFactory)
    				: new FeignLoadBalancer(lb, config, serverIntrospector);
    		this.cache.put(clientName, client);
    		return client;
    	}
    

    现在回到LoadBalancerFeignClient#execute方法块调用负载均衡的
    return lbClient(clientName) .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();的核心实现RetryableFeignLoadBalancer#execute

    在这里插入图片描述
    FeignLoadBalancer是RetryableFeignLoadBalancer的父类型,构造RetryableFeignLoadBalancer时调用的构造方法new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory)会调用父类的public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector) 方法,而RibbonClientConfiguration#ribbonClientConfig配置方法中所对应的IClientConfig会传入此构造方法的clientConfig参数,因此这里的this.connectTimeout this.readTimeout都是ribbon的超时时间。
    configOverride是feign超时配置或ribbon超时配置,而ribbon.connectTimeout(this.connectTimeout)this.connectTime超时参数是作为默认值的,所以说ribbon超时参数是会被覆盖掉,它的优先级低(feign、ribbon超时参数同时存在时)

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    安防监控视频AI智能分析网关:人流量统计算法的应用场景汇总
    css relative 和absolute布局
    632. 最小区间
    以“降本增效”为目标,智能视频监控能为企业带来哪些经济价值?
    MySql面试题总结
    你的团队工作量饱和吗?
    人人可懂的任务调度和可中断执行
    java计算机毕业设计基于安卓Android的订餐系统APP
    WPF创建自定义控件编译通过但是找不到资源
    大老耗时90天完成,带你从实践出发,深入学习Mycat中间件,拥抱Mycat
  • 原文地址:https://blog.csdn.net/Xiaowu_First/article/details/139399896