Spring cloud系列十五 使用執行緒池優化feign的http請求元件

hryou0922發表於2018-04-11

1. 概述

在預設情況下 spring cloud feign在進行各個子服務之間的呼叫時,http元件使用的是jdk的HttpURLConnection,沒有使用執行緒池。本文先從原始碼分析feign的http元件物件生成的過程,然後通過為feign配置http執行緒池優化呼叫效率。

2. 原始碼分析

我們分析原始碼spring cloud feign。在spring-cloud-netflix-core/META-INF/spring.factories中可以看到,在spring boot自動配置會初始化FeignRibbonClientAutoConfiguration,這個類會生成Ribbon的使用http元件。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration,\
複製程式碼

分析配置類是FeignRibbonClientAutoConfiguration 下面分析此類import的3個類:HttpClientFeignLoadBalancedConfiguration,OkHttpFeignLoadBalancedConfiguration,DefaultFeignLoadBalancedConfiguration

@Import({ HttpClientFeignLoadBalancedConfiguration.class,
	OkHttpFeignLoadBalancedConfiguration.class,
	DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
 …
}
複製程式碼

HttpClientFeignLoadBalancedConfiguration 為feigin配置appache client的執行緒池 當引入ApacheHttpClient.class類時,會初始化這個配置類 方法feignClient()中:根據@ConditionalOnMissingBean(Client.class)知道如果有HttpClient 物件,則建立的ApacheHttpClient使用自己定義的HttpClient 。如果沒有,則使用預設值。最後生成LoadBalancerFeignClient物件

@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
class HttpClientFeignLoadBalancedConfiguration {

	@Autowired(required = false)
	private HttpClient httpClient;

	@Bean
	@ConditionalOnMissingBean(Client.class)
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
		  SpringClientFactory clientFactory) {
		ApacheHttpClient delegate;
		if (this.httpClient != null) {
			delegate = new ApacheHttpClient(this.httpClient);
		} else {
			delegate = new ApacheHttpClient();
		}
		return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
	}
}

複製程式碼

OkHttpFeignLoadBalancedConfiguration 為feigin配置OkHttp,類似apache httpclient, 這裡略。 DefaultFeignLoadBalancedConfiguration 為feigin配置HttpURLConnection, 方法feignClient():只有以上兩個Client沒有生產物件時,才在這個方法中使用Client.Default生成LoadBalancerFeignClient

@Configuration
class DefaultFeignLoadBalancedConfiguration {
	@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
		  SpringClientFactory clientFactory) {
		return new LoadBalancerFeignClient(new Client.Default(null, null),
			cachingFactory, clientFactory);
	}
}
複製程式碼

檢視Client.Default的原始碼,Default 使用HttpURLConnection 建立連線且每次請求都建立一個新的連線

 public static class Default implements Client {
    @Override
    public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection).toBuilder().request(request).build();
    }
   ….
}
複製程式碼

綜上所述,在預設情況下,spring cloud 沒有引入httpclient和okhttp的jar包,所有預設使用HttpURLConnection

3. 使用appach httpclient執行緒池

預設情況下,服務之間呼叫使用的HttpURLConnection,效率非常低。為了提高效率,可以通過連線池提高效率,本節我們使用appache httpclient做為連線池。配置OkHttpClient連線池,也是類似的方法,這裡略。 經過上節的分析,配置執行緒池方法:引入appache httpclient並啟動對應配置,最後還需要生成HttpClient物件。

3.1. pom.xml中引入feign-httpclient.jar

<!-- 增加feign-httpclient -->
 <dependency>
     <groupId>io.github.openfeign</groupId>
     <artifactId>feign-httpclient</artifactId>
  </dependency>
複製程式碼

3.2. 配置引數application-hystrix-feign.yml啟動httpclient

# feign配置
feign:
  hystrix:
    # 在feign中開啟hystrix功能,預設情況下feign不開啟hystrix功能
    enabled: true
  ## 配置httpclient執行緒池
  httpclient:
    enabled: true
  okhttp:
    enabled: false
複製程式碼

3.3. 自定義配置類

使用配置類,生成HttpClient 物件。因為使用PoolingHttpClientConnectionManager連線池,我們需要啟動定時器,定時回收過期的連線。配置定時回收連線池的原因,見問題備忘: httpclient連線池異常引發的慘案

@Configuration
public class HttpPool {

    @Bean
    public HttpClient httpClient(){
        System.out.println("init feign httpclient configuration " );
        // 生成預設請求配置
        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
        // 超時時間
        requestConfigBuilder.setSocketTimeout(5 * 1000);
        // 連線時間
        requestConfigBuilder.setConnectTimeout(5 * 1000);
        RequestConfig defaultRequestConfig = requestConfigBuilder.build();
        // 連線池配置
        // 長連線保持30秒
        final PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.MILLISECONDS);
        // 總連線數
        pollingConnectionManager.setMaxTotal(5000);
        // 同路由的併發數
        pollingConnectionManager.setDefaultMaxPerRoute(100);

        // httpclient 配置
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // 保持長連線配置,需要在頭新增Keep-Alive
        httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
        httpClientBuilder.setConnectionManager(pollingConnectionManager);
        httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig);
        HttpClient client = httpClientBuilder.build();


        // 啟動定時器,定時回收過期的連線
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                //        System.out.println("=====closeIdleConnections===");
                pollingConnectionManager.closeExpiredConnections();
                pollingConnectionManager.closeIdleConnections(5, TimeUnit.SECONDS);
            }
        }, 10 * 1000, 5 * 1000);
        System.out.println("===== Apache httpclient 初始化連線池===");

        return client;
    }


}
複製程式碼

3.4. 測試:

啟動工程:cloud-registration-center、cloud-service-hystrix 啟動服務:HystrixFeignCloudConsumerApplication 執行請求:http://127.0.0.1:12082/hystrix-feign/simple

配置日誌為debug輸出(設定logback-spring.xml為 level為DEBUG),如果日誌有類似一下的輸出(包含PoolingHttpClientConnectionManager ),則表示連線池配置成功

2018-04-09 23:11:49.017 [hystrix-cloud-hystrix-service-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://192.168.0.113:12081][total kept alive: 0; route allocated: 0 of 100; total allocated: 0 of 5000]
2018-04-09 23:11:49.020 [hystrix-cloud-hystrix-service-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {}->http://192.168.0.113:12081][total kept alive: 0; route allocated: 1 of 100; total allocated: 1 of 5000]
複製程式碼

4. 程式碼

以上的詳細的程式碼見下面 github程式碼,請儘量使用tag v0.12,不要使用master,因為我不能保證master程式碼一直不變

相關文章