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:* <ul>* <li>Connect Timeout: 10 seconds</li>* <li>Read Timeout: 60 seconds</li>* <li>Follow all 3xx redirects</li>* </ul>*/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;@RibbonClientNameprivate String name = "client";
@Bean@ConditionalOnMissingBeanpublic 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 {@Overridepublic <T> DefaultClientConfigImpl set(IClientConfigKey<T> key, T value) {properties.put(key.key(), value);return this;}protected Object getProperty(String key) {//這里enableDynamicProperties一定是trueif (enableDynamicProperties) {String dynamicValue = null;DynamicStringProperty dynamicProperty = dynamicProperties.get(key);//dynamicProperty是nullif (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超時參數同時存在時)