OpenFeign是SpringCloud中的重要組件,它是一種聲明式的HTTP客戶端。使用OpenFeign調用遠程服務就像調用本地方法一樣,但是如果使用不當,很容易踩到坑。
坑一:用對Http Client
1.1 feign中http client
如果不做特殊配置,OpenFeign默認使用jdk自帶的HttpURLConnection,我們知道HttpURLConnection沒有連接池、性能和效率比較低,如果採用默認,很可能會遇到性能問題導致系統故障。
可以採用Apache HttpClient,properties文件中增加下面配置:
feign.httpclient.enabled=true
pom文件中增加依賴:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>9.3.1</version>
</dependency>
也可以採用OkHttpClient,properties文件中增加下面配置:
feign.okhttp.enabled=true
pom文件中增加依賴:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>10.2.0</version>
</dependency>
1.2 ribbon中的Http Client
通過OpenFeign作爲註冊中心的客戶端時,默認使用Ribbon做負載均衡,Ribbon默認也是用jdk自帶的HttpURLConnection,需要給Ribbon也設置一個Http client,比如使用okhttp,在properties文件中增加下面配置:
ribbon.okhttp.enabled=true
坑二:全局超時時間
OpenFeign可以設置超時時間,簡單粗暴,設置一個全局的超時時間,如下:
feign.client.config.default.connectTimeout=2000
feign.client.config.default.readTimeout=60000
如果不配置超時時間,默認是連接超時10s,讀超時60s,在源碼feign.Request的內部類Options中定義。
這個接口設置了最大的readTimeout是60s,這個時間必須大於調用的所有外部接口的readTimeout,否則處理時間大於readTimeout的接口就會調用失敗。
如下圖,在一個系統中使用OpenFeign調用外部三個服務,每個服務提供兩個接口,其中serviceC的一個接口需要60才能返回,那上面的readTimeout必須設置成60s。
但是如果serviceA出故障了,表現是接口1超過60s才能返回,這樣OpenFeign只能等到讀超時,如果調用這個接口的併發量很高,會大量佔用連接資源直到資源耗盡系統奔潰。要防止這樣的故障發生,就必須保證接口1能fail-fast。最好的做法就是給serviceC單獨設置超時時間。
坑三:單服務設置超時時間
從上一節的講解我們看到,需要對serviceC單獨設置一個超時時間,代碼如下:
feign.client.config.serviceC.connectTimeout=2000
feign.client.config.serviceC.readTimeout=60000
這個時間會覆蓋第一節中默認的超時時間。但是問題又來了,serviceC中又掉了serviceD,因爲serviceD的故障導致接口6發生了讀超時的情況,爲了不讓系統奔潰,不得不對serviceC的接口5單獨設置超時時間。如下圖:
坑四:熔斷超時時間
怎樣給單個接口設置超時時間,查看網上資料,必須開啓熔斷,配置如下:
feign.hystrix.enabled=true
開啓熔斷後,就可以給單個接口配置超時了。如果調用serviceC的接口5的聲明如下:
@FeignClient(value = "serviceC"configuration = FeignMultipartSupportConfig.class)
public interface ServiceCClient {
@GetMapping("/interface5")
String interface5(String param);
}
根據上面interface5接口的聲明,在properties文件中增加如下配置:
hystrix.command.ServiceCClient#interface5(param).execution.isolation.thread.timeoutInMilliseconds=60000
網上資料說的並不準確,這個超時時間並沒有起作用。爲什麼不生效呢?
4.1 使用feign超時
最終使用的超時時間來自於Options類。如果我們配置了feign的超時時間,會選擇使用feign超時時間,下面代碼在FeignClientFactoryBean類的configureUsingProperties方法:
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
}
4.2 使用ribbon超時
如果沒有配置feign,但是配置了ribbon的超時時間,會使用ribbon的超時時間。我們看下這段源代碼,FeignLoadBalancer裏面的execute方法,
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
throws IOException {
Request.Options options;
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);
}
//這個request裏面的client就是OkHttpClient
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
4.3 使用自定義Options
對於單個接口怎麼配置超時時間,我這裏給出一個方案,如果你有其他方案,歡迎探討。我的方案是使用RestTemplate來調這個接口,單獨配置超時時間,配置代碼如下,這裏使用OkHttpClient:
public class RestTemplateConfiguration {
@Bean
public OkHttp3ClientHttpRequestFactory okHttp3RequestFactory(){
OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory();
requestFactory.setConnectTimeout(2000);
requestFactory.setReadTimeout(60000);
return requestFactory;
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(OkHttp3ClientHttpRequestFactory okHttp3RequestFactory){
return new RestTemplate(okHttp3RequestFactory);
}
}
爲了使用ribbon負載均衡,上面加了@LoadBalanced
如果使用RestTemplate,就會使用OkHttp3ClientHttpRequestFactory中配置的時間。
坑五:ribbon超時時間
作爲負載均衡,ribbon超時時間也是可以配置的,可以在properties增加下面配置:
ribbon.ConnectTimeout=2000
ribbon.ReadTimeout=11000
有文章講ribbon配置的超時時間必須要滿足接口響應時間,其實不然,配置feign的超時時間就足夠了,因爲它可以覆蓋掉ribbon的超時時間。
坑六:重試默認不開啓
OpenFeign默認是不支持重試的,可以在源代碼FeignClientsConfiguration中feignRetryer中看出。
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
要開啓重試,我們可以自定義Retryer,比如下面這行代碼:
Retryer retryer = new Retryer.Default(100, 1000, 2);
表示每間隔100ms,最大間隔1000ms重試一次,最大重試次數是1,因爲第三個參數包含了第一次請求。
坑七:Ribbon重試
7.1 拉取服務列表
Ribbon默認從服務端拉取列表的時間間隔是30s,這個對優雅發佈很不友好,一般我們會把這個時間改短,如下改成3s:
serviceC.ribbon.ServerListRefreshInterval=3
7.2 重試
Ribbon重試有不少需要注意的地方,這裏分享4個。
1.同一實例最大重試次數,不包括首次調用,配置如下:
serviceC.ribbon.MaxAutoRetries=1
這個次數不包括首次調用,配置了1,重試策略會先嚐試在失敗的實例上重試一次,如果失敗,請求下一個實例。
2.同一個服務其他實例的最大重試次數,這裏不包括第一次調用的實例。默認值爲1:
serviceC.ribbon.MaxAutoRetriesNextServer=1
3.是否對所有操作都重試,如果改爲true,則對所有操作請求都進行重試,包括post,建議採用默認配置false。
serviceC.ribbon.OkToRetryOnAllOperations=false
4.對指定的http狀態碼進行重試
serviceC.retryableStatusCodes=404,408,502,500
坑八:hystrix超時
如下圖:
hystrix默認不開啓,但是如果開啓了hystrix,因爲hystrix是在Ribbon外面,所以超時時間需要符合下面規則:hystrix超時 >= (MaxAutoRetries + 1) * (ribbon ConnectTimeout + ribbon ReadTimeout)
如果Ribbon不重試,MaxAutoRetries=0
根據上面公式,假如我們配置熔斷超時時間如下:
hystrix.command.ServiceCClient#interface5(param).execution.isolation.thread.timeoutInMilliseconds=15000
ribbon.ReadTimeout=8000
這個配置是不會重試一次的。serviceA調用serviceB時,hystrix會等待Ribbon返回的結果,如果Ribbon配置了重試,hystrix會一直等待直到超時。上面的配置,因爲第一次請求已經耗去了8s,剩下時間7s不夠請求一次了,所以是不會進行重試的。
坑九:使用OpenFeign做http客戶端
即使不用註冊中心,使用OpenFeign做普通http客戶端也是很方便的,但是有三點需要注意:
- 不用配置ribbon相關參數
- 使用RestTemplate調用時,不考慮負載均衡
- 使用過程中OpenFeign要組裝出自己的一套請求,跟直接使用http客戶端比,會有一定開銷
使用OpenFeign有很多配置上的坑,對於沒有註冊中心的情況,建議直接使用http客戶端
https://cloud.tencent.com/developer/article/1866274