SpringCloud feign的http請(qǐng)求組件優(yōu)化

1 描述

  • 如果我們直接使用SpringCloud Feign進(jìn)行服務(wù)間調(diào)用的時(shí)候,http組件使用的是JDK的HttpURLConnection,每次請(qǐng)求都會(huì)新建一個(gè)連接,沒(méi)有使用線程池復(fù)用。具體的可以從源碼進(jìn)行分析

2 源碼分析

我們?cè)诜治鲈创a很難找到入口,不知道從何開(kāi)始入手,我們?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)行分析,從名字上來(lái)看我為了驗(yàn)證沒(méi)有特殊配置,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)求過(guò)來(lái)都會(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 自定義連接池
    可以通過(guò)代碼進(jìn)行配置,也可以通過(guò)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é)果

如下所示:


5457aa9f869ed2f123267b0077e156c5.png

彩蛋

  • okhttp客戶端會(huì)走的代碼可以看出來(lái)okhttp有synchronized鎖線程安全的那默認(rèn)的是否是線程安全的呢 有待去驗(yàn)證。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容