Spring Cloud 源码解读之 如何配置好OpenFeign的各种超时时间!

2022-08-06 11:46:45

关于Feign的超时详解:

Spring Cloud微服务架构中,大部分公司都是利用Open Feign进行服务间的调用,而比较简单的业务使用默认配置是不会有多大问题的,但是如果是业务比较复杂,服务要进行比较繁杂的业务计算,那后台很有可能会出现Read Timeout这个异常。

1、关于hystrix的熔断超时

如果Feign开启了熔断,必须要重新设置熔断超时的时间,因为默认的熔断超时时间太短了,只有1秒,这容易导致业务服务的调用还没完成然后超时就被熔断了。

如何配置熔断超时:

#Feign如何开启熔断
feign.hystrix.enabled=true

#是否开始超时熔断,如果为false,则熔断机制只在服务不可用时开启(spring-cloud-starter-openfeign中的HystrixCommandProperties默认为true)
hystrix.command.default.execution.timeout.enabled=true

#设置超时熔断时间(spring-cloud-starter-openfeign中的HystrixCommandProperties默认为1000毫秒)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000

注意:关于hystrixapplication.properties配置是没提示的,但是HystrixCommandProperties是会获取的。

// 构造函数protectedHystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix){// .... 省略很多其他配置// propertyPrefix:hystrix,key:defaultthis.executionTimeoutInMilliseconds=getProperty(propertyPrefix, key,"execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);}// 具体获取属性的方法privatestatic HystrixProperty<String>getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, String builderOverrideValue, String defaultValue){return HystrixPropertiesChainedProperty.forString().add(propertyPrefix+".command."+ key.name()+"."+ instanceProperty, builderOverrideValue).add(propertyPrefix+".command.default."+ instanceProperty, defaultValue).build();}

2、关于Ribbon超时。

Feign调用默认是使用Ribbon进行负载均衡的,所以我们还需要了解关于Ribbon的超时。

①、Feign的调用链路

看一下Feign的请求是否有使用Ribbon的超时时间,而且是如何读取Ribbon的超时时间的?

(1)、org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute

(2)、com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)

(3)、org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory#create

	创建Client,这里会判断对应ClientName的链接Client是否创建过,如果创建过复用之前的Client;
	如果不存在则创建一个并且放入cache缓存。
public FeignLoadBalancercreate(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= loadBalancedRetryFactory!= null?newRetryableFeignLoadBalancer(lb, config, serverIntrospector,
			loadBalancedRetryFactory):newFeignLoadBalancer(lb, config, serverIntrospector);this.cache.put(clientName, client);return client;}

(4)、com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)

	负载均衡器抽象类

(5)、org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute

	Feign的负载均衡器实现类。到这里我们可以看到,连接超时和读超时的配置都在这里:
	如果application.properties配置文件中的超时时间不为空,则使用配置的超时时间。
	如果为空则使用默认值,而从FeignLoadBalancer的构造函数可以看到,默认值也是取的RibbonProperties的默认超时时间。
public RibbonResponseexecute(RibbonRequest request, IClientConfig configOverride)throws IOException{
    Request.Options options;// 设置超时时间。,如果orride的配置为空,则用默认值if(configOverride!= null){
        RibbonProperties override= RibbonProperties.from(configOverride);
        options=newRequest.Options(
            override.connectTimeout(this.connectTimeout),
            override.readTimeout(this.readTimeout));}else{
        options=newRequest.Options(this.connectTimeout,this.readTimeout);}// 发起请求
    Response response= request.client().execute(request.toRequest(), options);returnnewRibbonResponse(request.getUri(), response);}// 构造函数publicFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector){super(lb, clientConfig);this.setRetryHandler(RetryHandler.DEFAULT);this.clientConfig= clientConfig;this.ribbon= RibbonProperties.from(clientConfig);
    RibbonProperties ribbon=this.ribbon;this.connectTimeout= ribbon.getConnectTimeout();this.readTimeout= ribbon.getReadTimeout();this.serverIntrospector= serverIntrospector;}

②、Ribbon的默认超时时间

RibbonClientConfiguration中:

publicstaticfinalint DEFAULT_CONNECT_TIMEOUT=1000;publicstaticfinalint DEFAULT_READ_TIMEOUT=1000;

③、如何自定义Ribbon超时时间

首先,RibbonProperties的超时时间的读取的源码如下:

public IntegergetConnectTimeout(){return(Integer)this.get(CommonClientConfigKey.ConnectTimeout);}public IntegergetReadTimeout(){return(Integer)this.get(CommonClientConfigKey.ReadTimeout);}

然后,可以在CommonClientConfigKey中可以看到两个超时时间的名称:

// ConnectTimeout:publicstaticfinal IClientConfigKey<Integer> ConnectTimeout=newCommonClientConfigKey<Integer>("ConnectTimeout"){};// ReadTimeout:publicstaticfinal IClientConfigKey<Integer> ReadTimeout=newCommonClientConfigKey<Integer>("ReadTimeout"){};

然后,在IClientConfig的默认实现类:DefaultClientConfigImpl中,可以发现Ribbon配置的前缀

publicstaticfinal String DEFAULT_PROPERTY_NAME_SPACE="ribbon";

所以,最后Ribbon该这么配置超时时间:

ribbon.ConnectTimeout=5000
ribbon.ReadTimeout=5000

总结

如何配置好HystrixRibbon的超时时间呢?
其实是有套路的。因为Feign的请求:其实是Hystrix+RibbonHystrix在最外层,然后再到Ribbon,最后里面的是http请求。所以说。Hystrix的熔断时间必须大于Ribbon的 (ConnectTimeout +ReadTimeout )。而如果Ribbon开启了重试机制,还需要乘以对应的重试次数,保证在Ribbon里的请求还没结束时,Hystrix的熔断时间不会超时。

  • 作者:不送花的程序猿
  • 原文链接:https://blog.csdn.net/Howinfun/article/details/103836183
    更新时间:2022-08-06 11:46:45