http連接池配置及spring boot restTemplate配置http連接池


本文為博主原創,轉載請注明出處:

  項目中存在第三方系統之間的服務調用通信,且會進行頻繁調用,由於很早之前實現的調用方式為每調用一次外部接口,就需要新建一個HttpClient 對象。由於頻繁調用,會存在性能問題。

針對這種場景,進行優化,使用httpClient 連接池,避免重復頻繁創建httpClient 造成性能問題。以下為簡單實現的demo:

  1. 對 httpClient 的屬性及常用配置封裝 HttpPoolProperties 

package com.example.demo.config;

import lombok.Data;
import org.springframework.stereotype.Component;

@Component
//@ConfigurationProperties(prefix = "http.pool.conn") // 可在配置文件中進行配置
@Data
public class HttpPoolProperties {
    // 最大連接數
    private Integer maxTotal = 20;
    // 同路由並發數
    private Integer defaultMaxPerRoute =20 ;
    private Integer connectTimeout = 2000;
    private Integer connectionRequestTimeout=2000;
    private Integer socketTimeout= 2000;
    // 線程空閑多久后進行校驗
    private Integer validateAfterInactivity= 2000;
    // 重試次數
    private Integer retryTimes = 2;

    // 是否開啟充實
    private boolean enableRetry = true;
    // 重試的間隔:可實現 ServiceUnavailableRetryStrategy 接口
    private Integer retryInterval= 2000;
}

 

        2.  創建httpClient 連接池,並對RestTemplate 指定httpClient 及連接池

  

package com.example.demo.util;

import com.example.demo.config.HttpPoolProperties;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

@Configuration
public class HttpClientPoolUtils {

    @Autowired
    private HttpPoolProperties httpPoolProperties;

    /**
     * 首先實例化一個連接池管理器,設置最大連接數、並發連接數
     * @return
     */
    @Bean(name = "httpClientConnectionManager")
    public PoolingHttpClientConnectionManager getHttpClientConnectionManager(){
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();

        PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);
        //最大連接數
        httpClientConnectionManager.setMaxTotal(httpPoolProperties.getMaxTotal());
        //並發數
        httpClientConnectionManager.setDefaultMaxPerRoute(httpPoolProperties.getDefaultMaxPerRoute());

        httpClientConnectionManager.setValidateAfterInactivity(httpPoolProperties.getValidateAfterInactivity());

        return httpClientConnectionManager;
    }

    /**
     * 實例化連接池,設置連接池管理器。
     * 這里需要以參數形式注入上面實例化的連接池管理器
     * @param httpClientConnectionManager
     * @return
     */
    @Bean(name = "httpClientBuilder")
    public HttpClientBuilder getHttpClientBuilder(@Qualifier("httpClientConnectionManager")PoolingHttpClientConnectionManager httpClientConnectionManager){

        //HttpClientBuilder中的構造方法被protected修飾,所以這里不能直接使用new來實例化一個HttpClientBuilder,可以使用HttpClientBuilder提供的靜態方法create()來獲取HttpClientBuilder對象
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

        httpClientBuilder.setConnectionManager(httpClientConnectionManager);

        if (httpPoolProperties.isEnableRetry()){
            // 重試次數
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpPoolProperties.getRetryTimes(), true));
            // 若需要自定義http 的重試策略,可以重新實現ServiceUnavailableRetryStrategy 或 HttpRequestRetryHandler接口,比如對指定異常或制定狀態碼進行重試,並指定充實的次數。
        }else {
            httpClientBuilder.disableAutomaticRetries();
        }
        // 另外httpClientBuilder 可以設置長連接策略,dns解析器,代理,攔截器以及UserAgent等等。可根據業務需要進行實現

        return httpClientBuilder;
    }


    /* 注入連接池,用於獲取httpClient
     * @param httpClientBuilder
     * @return
     */
    @Bean("httpClient")
    public CloseableHttpClient httpClient(@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder){
        return httpClientBuilder.build();
    }

    /**
     * Builder是RequestConfig的一個內部類
     * 通過RequestConfig的custom方法來獲取到一個Builder對象
     * 設置builder的連接信息
     * 這里還可以設置proxy,cookieSpec等屬性。有需要的話可以在此設置
     * @return
     */
    @Bean(name = "builder")
    public RequestConfig.Builder getBuilder(){
        RequestConfig.Builder builder = RequestConfig.custom();
        return builder.setConnectTimeout(httpPoolProperties.getConnectTimeout()) //連接上服務器(握手成功)的時間,超出拋出connect timeout
                //從連接池中獲取連接的超時時間,超時間未拿到可用連接,會拋出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
                .setConnectionRequestTimeout(httpPoolProperties.getConnectionRequestTimeout())
                //服務器返回數據(response)的時間,超過拋出read timeout
                .setSocketTimeout(httpPoolProperties.getSocketTimeout());
    }

    /**
     * 使用builder構建一個RequestConfig對象
     * @param builder
     * @return
     */
    @Bean
    public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder){
        return builder.build();
    }

    /**
     * RestTemplate 指定httpClient 及連接池
     * 
     * @param httpClient
     * @return
     */
    @Bean(name = "httpClientTemplate")
    public RestTemplate restTemplate(@Qualifier("httpClient") CloseableHttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setHttpClient(httpClient);
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(factory);
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
        return restTemplate;
    }

}

 

  3。 創建清理線程對httpClient 空閑線程,失效線程進行清理

package com.example.demo.util;

import org.apache.http.conn.HttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class IdleConnectionEvictor extends Thread {
    @Autowired
    private HttpClientConnectionManager connMgr;

    private volatile boolean shutdown;

    public IdleConnectionEvictor() {
        super();
        super.start();
    }

    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    wait(5000);
                    // 關閉失效的連接
                    connMgr.closeExpiredConnections();
                }
            }
        } catch (InterruptedException ex) {
            // 結束
        }
    }

    //關閉清理無效連接的線程
    public void shutdown() {
        shutdown = true;
        synchronized (this) {
            notifyAll();
        }
    }
}

   

  4. 單元測試

package com.example.demo;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;

@Slf4j
@SpringBootTest
public class HttpTest {

    @Autowired
    private RestTemplate httpClientTemplate;

    @Autowired
    private CloseableHttpClient httpClient;

    @Test
    void test() throws IOException {
String result
= httpClientTemplate.getForObject("https://www.baidu.com/",String.class); System.out.println("httpClientTemplate==="+result);

// 聲明 http get 請求 String url = "https://www.baidu.com/"; HttpGet httpGet = new HttpGet(url); // 發起請求 CloseableHttpResponse response = this.httpClient.execute(httpGet); System.out.println("httpClient==="+response); } }

  測試方法分別通過CloseableHttpClient   httpClient  進行http調用與自定義的RestTemplate httpClientTemplate 進行 http 調用。

   執行結果如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM