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 调用。

   执行结果如下:

 

posted @ 2021-03-13 23:02  香吧香  阅读(5300)  评论(0编辑  收藏  举报