淺談SpringCloud feign的http請(qǐng)求組件優(yōu)化方案
1 描述
如果我們直接使用SpringCloud Feign進(jìn)行服務(wù)間調(diào)用的時(shí)候,http組件使用的是JDK的HttpURLConnection,每次請(qǐng)求都會(huì)新建一個(gè)連接,沒有使用線程池復(fù)用。具體的可以從源碼進(jìn)行分析
2 源碼分析
我們?cè)诜治鲈创a很難找到入口,不知道從何開始入手,我們?cè)诜治鯯pringCloud feign的時(shí)候可用在配置文件下面我講一下個(gè)人的思路。
1 首先我點(diǎn)擊@EnableFeignClients 看一下這個(gè)注解在哪個(gè)資源路徑下
如下圖所示:

2 找到服務(wù)啟動(dòng)加載的配置文件

3 因?yàn)閒eign底層的負(fù)載均衡是基于Ribbon的所以很快就找到了FeignRibbonClientAutoConfiguration.java 這個(gè)類
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
//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({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
首先我們從這三個(gè)類進(jìn)行分析,從名字上來看我為了驗(yàn)證沒有特殊配置,feign底層走的是不是默認(rèn)的DefaultFeignLoadBalancedConfiguration.class
OkHttpFeignLoadBalancedConfiguration.class
HttpClientFeignLoadBalancedConfiguration.class
DefaultFeignLoadBalancedConfiguration.class
DefaultFeignLoadBalancedConfiguration.class
@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}
從上面代碼可知每次請(qǐng)求過來都會(huì)創(chuàng)建一個(gè)新的client,具體的源碼演示有興趣的可以深入研究,在這里不是我們所研究的重點(diǎn)。
OkHttpFeignLoadBalancedConfiguration.class
@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "feign.okhttp.enabled")
class OkHttpFeignLoadBalancedConfiguration {
@Configuration
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
protected static class OkHttpFeignConfiguration {
private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
Integer maxTotalConnections = httpClientProperties.getMaxConnections();
Long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
}
@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
Boolean followRedirects = httpClientProperties.isFollowRedirects();
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).
connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
followRedirects(followRedirects).
connectionPool(connectionPool).build();
return this.okHttpClient;
}
@PreDestroy
public void destroy() {
if(okHttpClient != null) {
okHttpClient.dispatcher().executorService().shutdown();
okHttpClient.connectionPool().evictAll();
}
}
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
從源碼可以看出
1 該類是個(gè)配置類,當(dāng)引入OkHttpClient.Class會(huì)加載
client方法中可以看出會(huì)返回一個(gè)http連接池的client
HttpClientFeignLoadBalancedConfiguration
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
class HttpClientFeignLoadBalancedConfiguration {
這個(gè)類和OkHttpFeignLoadBalancedConfiguration原理類型
使用OKHttp替代默認(rèn)的JDK的HttpURLConnection
使用appach httpclient使用教程類似
使用方法
1 pom
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
2 Yml文件
feign: okhttp: enabled: true
3 自定義連接池
可以通過代碼進(jìn)行配置,也可以通過yml配置
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {
@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
.readTimeout(60,TimeUnit.SECONDS)
.connectTimeout(60,TimeUnit.SECONDS)
.connectionPool(new ConnectionPool())
.build();
}
}
驗(yàn)證
默認(rèn)的Feign處理會(huì)走到如下位置;
位置處于如下圖所示

@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}
走okhttp客戶端會(huì)走如下代碼
具體位置如下圖所示:

@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
驗(yàn)證結(jié)果
如下所示:

彩蛋
okhttp客戶端會(huì)走的代碼可以看出來okhttp有synchronized鎖線程安全的那默認(rèn)的是否是線程安全的呢 有待去驗(yàn)證。
追加
如果發(fā)現(xiàn)配置的超時(shí)時(shí)間無效,可以添加以下配置,因?yàn)樽x取超時(shí)配置的時(shí)候沒有讀取上面的okhttp的配置參數(shù),而是從Request中讀取。
具體配置如下所示:
@Bean
public Request.Options options(){
return new Request.Options(60000,60000);
}
補(bǔ)充:springCloud feign使用/優(yōu)化總結(jié)
基于springCloud Dalston.SR3版本
1.當(dāng)接口參數(shù)是多個(gè)的時(shí)候 需要指定@RequestParam 中的value來明確一下。
/**
* 用戶互掃
* @param uid 被掃人ID
* @param userId 當(dāng)前用戶ID
* @return
*/
@PostMapping(REQ_URL_PRE + "/qrCodeReturnUser")
UserQrCode qrCodeReturnUser(@RequestParam("uid") String uid,@RequestParam("userId") Integer userId);
2.接口參數(shù)為對(duì)象的時(shí)候 需要使用@RequestBody注解 并采用POST方式。
3.如果接口是簡單的數(shù)組/列表參數(shù) 這里需要使用Get請(qǐng)求才行
@GetMapping(REQ_URL_PRE + "/getUserLevels")
Map<Integer, UserLevel> getUserLevels(@RequestParam("userIds") List<Integer> userIds);
4.直接可以在@FeignClient中配置降級(jí)處理方式 對(duì)于一些不重要的業(yè)務(wù) 自定義處理很有幫助
@FeignClient(value = "cloud-user", fallback = IUsers.UsersFallback.class)
5.feign默認(rèn)只有HystrixBadRequestException異常不會(huì)走熔斷,其它任何異常都會(huì)進(jìn)入熔斷,需要重新實(shí)現(xiàn)一下ErrorDecoder包裝業(yè)務(wù)異常
示例:https://github.com/peachyy/feign-support
6. feign HTTP請(qǐng)求方式選擇
feign默認(rèn)使用的是基于JDK提供的URLConnection調(diào)用HTTP接口,不具備連接池。所以資源開銷上有點(diǎn)影響,經(jīng)測(cè)試JDK的URLConnection比Apache HttpClient快很多倍。但是Apache HttpClient和okhttp都支持配置連接池功能。具體選擇需要權(quán)衡
7.默認(rèn)不啟用hystrix 需要手動(dòng)指定feign.hystrix.enabled=true 開啟熔斷
8.啟用壓縮也是一種有效的優(yōu)化方式
feign.compression.request.enabled=true feign.compression.response.enabled=true feign.compression.request.mime-types=text/xml,application/xml,application/json
9.參數(shù)相關(guān)調(diào)優(yōu)
hystrix線程數(shù)設(shè)置
設(shè)置參數(shù)hystrix.threadpool.default.coreSize 來指定熔斷隔離的線程數(shù) 這個(gè)數(shù)需要調(diào)優(yōu),經(jīng)測(cè)試 線程數(shù)我們?cè)O(shè)置為和提供方的容器線程差不多,吞吐量高許多。
第一次訪問服務(wù)出錯(cuò)的問題
啟用Hystrix后,很多服務(wù)當(dāng)?shù)谝淮卧L問的時(shí)候都會(huì)失敗 是因?yàn)槌跏蓟?fù)載均衡一系列操作已經(jīng)超出了超時(shí)時(shí)間了 默認(rèn)的超時(shí)時(shí)間為1S,設(shè)置參數(shù)超時(shí)時(shí)間hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=30000 可解決這個(gè)問題。
負(fù)載均衡參數(shù)設(shè)置
設(shè)置了Hystrix的超時(shí)參數(shù)會(huì) 還需設(shè)置一下ribbon的相關(guān)參數(shù) 這些參數(shù)和Hystrix的超時(shí)參數(shù)有一定的邏輯關(guān)系
請(qǐng)求處理的超時(shí)時(shí)間 ribbon.ReadTimeout=120000
請(qǐng)求連接的超時(shí)時(shí)間 ribbon.ConnectTimeout=30000
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
使用Cloud?Studio構(gòu)建SpringSecurity權(quán)限框架(騰訊云?Cloud?Studio?實(shí)戰(zhàn)訓(xùn)練
隨著云計(jì)算技術(shù)的成熟和普及,傳統(tǒng)編程能力和資源以云服務(wù)的形式開放出來,從中間件、數(shù)據(jù)庫等水平能力服務(wù)組件到人臉識(shí)別、鑒權(quán)服務(wù)等基本業(yè)務(wù)服務(wù)組件很容易的在云端獲取,本文介紹使用Cloud?Studio構(gòu)建SpringSecurity權(quán)限框架的相關(guān)知識(shí),感興趣的朋友一起看看吧2023-08-08
java利用反射實(shí)現(xiàn)動(dòng)態(tài)代理示例
這篇文章主要介紹了java利用反射實(shí)現(xiàn)動(dòng)態(tài)代理示例,需要的朋友可以參考下2014-04-04
Java網(wǎng)絡(luò)編程之IO模型阻塞與非阻塞簡要分析
這篇文章主要介紹Java網(wǎng)絡(luò)編程中的IO模型阻塞與非阻塞簡要分析,文中附有示例代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-09-09
Springboot與vue實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出方法具體介紹
這篇文章主要介紹了Springboot與vue實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02
MyBatis 探秘之#{} 與 ${} 參傳差異解碼(數(shù)據(jù)庫連接池筑牢數(shù)據(jù)交互
本文詳細(xì)介紹了MyBatis中的`#{}`和`${}`的區(qū)別與使用場景,包括預(yù)編譯SQL和即時(shí)SQL的區(qū)別、安全性問題,以及如何正確使用數(shù)據(jù)庫連接池來提高性能,感興趣的朋友一起看看吧2024-12-12
IDEA中properties與yml文件的轉(zhuǎn)變方式
文章介紹了如何在IntelliJ IDEA 2021.1.1中安裝和使用ConvertYAMLandPropertiesFile插件進(jìn)行YAML和Properties文件之間的轉(zhuǎn)換,安裝步驟包括導(dǎo)航到設(shè)置、安裝插件、找到并安裝插件等,插件支持從Properties文件轉(zhuǎn)換為YAML文件,但轉(zhuǎn)換過程中會(huì)丟失注釋2024-12-12

