關於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
注意:關於hystrix
在application.properties
配置是沒提示的,但是HystrixCommandProperties
是會獲取的。
// 構造函數
protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) {
// .... 省略很多其他配置
// propertyPrefix:hystrix,key:default
this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);
}
// 具體獲取屬性的方法
private static 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、Feign局部設置超時間
spring-cloud-dependencies Dalston版本之後,默認Feign對Hystrix的支持默認是關閉的,需要手動開啓。
feign.hystrix.enabled=true
開啓hystrix,可以選擇關閉熔斷或超時。
2.1關閉熔斷:
# 全局關閉熔斷:
hystrix.command.default.circuitBreaker.enabled: false
# 局部關閉熔斷:
hystrix.command.<HystrixCommandKey>.circuitBreaker.enabled: false
2.2設置超時:
# 全局設置超時:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 1000
# 局部設置超時:
hystrix.command.<HystrixCommandKey>.execution.isolation.thread.timeoutInMilliseconds: 1000
2.3設置局部方法超時成功案例:
需要對下圖兩個方法局部設置超時時間爲10秒,
方法一、配置如下:
# 禁用Hystrix超時
hystrix.threadpool.default.coreSize = 10
hystrix.command.default.fallback.enabled = true
hystrix.command.default.execution.timeout.enabled= true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 3000
hystrix.command.MemberStaffFeign#getExcelDataByDoctor(String,Integer,Integer).execution.isolation.thread.timeoutInMilliseconds=10000
hystrix.command.MemberStaffFeign#getExcelDataByTeam(String,Integer,Integer).execution.isolation.thread.timeoutInMilliseconds=10000
@SpringBootApplication啓動類中添加@EnableCircuitBreaker註解
@EnableFeignClients
@EnableEurekaClient
@EnableDiscoveryClient
@EnableApolloConfig
@EnableCircuitBreaker
@SpringBootApplication
public class BusinessSystemApplication {
public static void main(String[] args) {
ConfigurableApplicationContext app = new SpringApplicationBuilder(BusinessSystemApplication.class).run(args);
System.out.println(app.getEnvironment().getProperty("spring.application.name") + "服務啓動完畢...");
}
}
2.4關閉超時
# 全局關閉:
hystrix.command.default.execution.timeout.enabled: false
# 局部關閉:
hystrix.command.<HystrixCommandKey>.execution.timeout.enabled: false
3、關於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 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 = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryFactory) : new FeignLoadBalancer(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 RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
// 設置超時時間。,如果orride的配置爲空,則用默認值
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(
override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
// 發起請求
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
// 構造函數
public FeignLoadBalancer(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
中:
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
public static final int DEFAULT_READ_TIMEOUT = 1000;
③、如何自定義Ribbon超時時間
首先,RibbonProperties
的超時時間的讀取的源碼如下:
public Integer getConnectTimeout() {
return (Integer)this.get(CommonClientConfigKey.ConnectTimeout);
}
public Integer getReadTimeout() {
return (Integer)this.get(CommonClientConfigKey.ReadTimeout);
}
然後,可以在CommonClientConfigKey
中可以看到兩個超時時間的名稱:
// ConnectTimeout:
public static final IClientConfigKey<Integer> ConnectTimeout = new CommonClientConfigKey<Integer>("ConnectTimeout") {};
// ReadTimeout:
public static final IClientConfigKey<Integer> ReadTimeout = new CommonClientConfigKey<Integer>("ReadTimeout") {};
然後,在IClientConfig
的默認實現類:DefaultClientConfigImpl
中,可以發現Ribbon
配置的前綴
public static final String DEFAULT_PROPERTY_NAME_SPACE = "ribbon";
所以,最後Ribbon
該這麼配置超時時間:
ribbon.ConnectTimeout=5000
ribbon.ReadTimeout=5000
總結
1.如何配置好Hystrix
和Ribbon
的超時時間呢?
其實是有套路的,因爲Feign
的請求:其實是Hystrix
+Ribbon
。Hystrix
在最外層,然後再到Ribbon
,最後裏面的是http
請求。所以說。Hystrix
的熔斷時間必須大於Ribbon
的 ( ConnectTimeout
+ ReadTimeout
)。而如果Ribbon
開啓了重試機制,還需要乘以對應的重試次數,保證在Ribbon
裏的請求還沒結束時,Hystrix
的熔斷時間不會超時。