zoukankan      html  css  js  c++  java
  • 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 调用。

       执行结果如下:

  • 相关阅读:
    注解的那些事儿(三)| 注解的使用
    注解的那些事儿(二)| 如何自定义注解
    注解的那些事儿(一)| 为什么要使用注解?
    数据库历险记(三) | 缓存框架的连环炮
    书值 | 第 2 期:成为技术管理者,思维上应该如何转变?
    书值 | 第 1 期:如何在1年内完成大学四年的课程?
    网站被篡改详细处理方法
    【代码审计】任意文件读取漏洞实例
    XSS三重URL编码绕过实例
    代码审计之DocCms漏洞分析
  • 原文地址:https://www.cnblogs.com/zjdxr-up/p/14530875.html
Copyright © 2011-2022 走看看