springboot restTemplate连接池整合方式

 更新时间:2021年10月23日 16:00  点击:2333 作者:程猿薇茑

springboot restTemplate连接池整合

restTemplate

使用http连接池能够减少连接建立与释放的时间,提升http请求的性能。如果客户端每次请求都要和服务端建立新的连接,即三次握手将会非常耗时。本文介绍如何在Springboot中集成http连接池;基于restTemplate+httpclient实现。

引入apache httpclient

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>

RestTemplate配置类

import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * 实际开发中要避免每次http请求都实例化httpclient
 * restTemplate默认会复用连接,保证restTemplate单例即可
 * 参考资料:
 * https://www.cnblogs.com/xrq730/p/10963689.html
 * https://halfrost.com/advance_tcp/
 */
@Configuration
public class RestTemplateConfig {
    @Bean
    RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        for (HttpMessageConverter c : messageConverters) {
            if (c instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) c).setDefaultCharset(Charset.forName("utf-8"));
            }
        }
        return restTemplate;
    }
    @Bean
    @ConfigurationProperties(prefix = "spring.resttemplate")
    HttpClientProperties httpClientProperties() {
        return new HttpClientProperties();
    }
    @Bean
    ClientHttpRequestFactory clientHttpRequestFactory(HttpClientProperties httpClientProperties) {
        //如果不使用HttpClient的连接池,则使用restTemplate默认的SimpleClientHttpRequestFactory,底层基于HttpURLConnection
        if (!httpClientProperties.isUseHttpClientPool()) {
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(httpClientProperties.getConnectTimeout());
            factory.setReadTimeout(httpClientProperties.getReadTimeout());
            return factory;
        }
        //HttpClient4.3及以上版本不手动设置HttpClientConnectionManager,默认就会使用连接池PoolingHttpClientConnectionManager
        HttpClient httpClient = HttpClientBuilder.create().setMaxConnTotal(httpClientProperties.getMaxTotalConnect())
                .setMaxConnPerRoute(httpClientProperties.getMaxConnectPerRoute()).evictExpiredConnections()
                .evictIdleConnections(5000, TimeUnit.MILLISECONDS).build();
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
        factory.setConnectTimeout(httpClientProperties.getConnectTimeout());
        factory.setReadTimeout(httpClientProperties.getReadTimeout());
        factory.setConnectionRequestTimeout(httpClientProperties.getConnectionRequestTimeout());
        return factory;
    }
}

RestTemplate连接池配置参数

public class HttpClientProperties {
    /**
     * 是否使用httpclient连接池
     */
    private boolean useHttpClientPool = false;
    /**
     * 从连接池中获得一个connection的超时时间
     */
    private int connectionRequestTimeout = 3000;
    /**
     * 建立连接超时时间
     */
    private int connectTimeout = 3000;
    /**
     * 建立连接后读取返回数据的超时时间
     */
    private int readTimeout = 5000;
    /**
     * 连接池的最大连接数,0代表不限
     */
    private int maxTotalConnect = 128;
    /**
     * 每个路由的最大连接数
     */
    private int maxConnectPerRoute = 32;
    public int getConnectionRequestTimeout() {
        return connectionRequestTimeout;
    }
    public void setConnectionRequestTimeout(int connectionRequestTimeout) {
        this.connectionRequestTimeout = connectionRequestTimeout;
    }
    public int getConnectTimeout() {
        return connectTimeout;
    }
    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }
    public int getReadTimeout() {
        return readTimeout;
    }
    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }
    public int getMaxTotalConnect() {
        return maxTotalConnect;
    }
    public void setMaxTotalConnect(int maxTotalConnect) {
        this.maxTotalConnect = maxTotalConnect;
    }
    public int getMaxConnectPerRoute() {
        return maxConnectPerRoute;
    }
    public void setMaxConnectPerRoute(int maxConnectPerRoute) {
        this.maxConnectPerRoute = maxConnectPerRoute;
    }
    public boolean isUseHttpClientPool() {
        return useHttpClientPool;
    }
    public void setUseHttpClientPool(boolean useHttpClientPool) {
        this.useHttpClientPool = useHttpClientPool;
    }
}

application.properties

spring.resttemplate.connectionRequestTimeout=3000
spring.resttemplate.connectTimeout=3000
spring.resttemplate.readTimeout=10000
spring.resttemplate.maxTotalConnect=256
spring.resttemplate.maxConnectPerRoute=128
spring.resttemplate.useHttpClientPool=true

测试带连接池的RestTemplate

import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RestTemplateTest {
    /**
     * 免费查询号码归属地接口
     */
    public String testUrl = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm";
    @Autowired
    RestTemplate restTemplate;
    @Test
    public void testRest() {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Accept", "application/json");
        HttpEntity entity = new HttpEntity(headers);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            String tel = getRandomTel();
            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(testUrl).queryParam("tel", tel);
            System.out.println("发送请求:" + builder.build().encode().toUri());
            long startInner = System.currentTimeMillis();
            ResponseEntity<String> getDistrictRes = restTemplate.exchange(builder.build().encode().toUri(), HttpMethod.GET, entity, String.class);
            long endInner = System.currentTimeMillis();
            System.out.print("costPerRequest:" + (endInner - startInner) + ",i=" + i + "," + Thread.currentThread().getName());
            String resJson = getDistrictRes.getBody().split("=")[1];
            String carrier = (String) JSON.parseObject(resJson).get("carrier");
            System.out.println("," + tel + ",归属地:" + carrier);
        }
        long end = System.currentTimeMillis();
        System.out.println("costTotal:" + (end - start));
    }
    private String getRandomTel() {
        List<String> telList = Arrays.asList("18120168516", "15952044278", "15537788259", "18751872329", "13913329187");
        int index = ThreadLocalRandom.current().nextInt(telList.size());
        return telList.get(index);
    }
}

测试比较发现,如果不设置ClientHttpRequestFactory,resttemplate默认会使用SimpleClientHttpRequestFactory,底层基于HttpURLConnection;这种方式和手动设置带连接池的httpComponentsClientHttpRequestFactory性能差别不大,基于httpclient的连接池性能稍有优势,不是太明显。

不管是使用restTemplate默认的SimpleClientHttpRequestFactory还是使用httpclient提供的HttpComponentsClientHttpRequestFactory,都会进行连接复用,即只有第一次请求耗时较高,后面的请求都复用连接。

使用httpclient可以设置evictExpiredConnections、evictIdleConnections进行定时清理过期、闲置连接。底层是开启了一个线程去执行清理任务,因此注意不能多次实例化httpclient相关的实例,会导致不断创建线程。

注意事项

实际开发中要避免每次http请求都实例化httpclient

restTemplate默认会复用连接,保证restTemplate单例即

RestTemplate 配置http连接池

import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
 
@Configuration
public class RestTemplateUtil{ 
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
	RestTemplate restTemplate = builder.build();
	restTemplate.setRequestFactory(clientHttpRequestFactory()); 
        // 使用 utf-8 编码集的 conver 替换默认的 conver(默认的 string conver 的编码集为"ISO-8859-1")
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator();
        while (iterator.hasNext()) {
            HttpMessageConverter<?> converter = iterator.next();
            if (converter instanceof StringHttpMessageConverter) {
                iterator.remove();
            }
        }
        messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); 
        return restTemplate;
    }	
    
    @Bean
    public HttpClientConnectionManager poolingConnectionManager() {
	PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
	poolingConnectionManager.setMaxTotal(1000); // 连接池最大连接数  
	poolingConnectionManager.setDefaultMaxPerRoute(100); // 每个主机的并发
	return poolingConnectionManager;
    }
    
    @Bean
    public HttpClientBuilder httpClientBuilder() {
	HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        //设置HTTP连接管理器
	httpClientBuilder.setConnectionManager(poolingConnectionManager());
	return httpClientBuilder;
    }
    
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory() { 
	HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
	clientHttpRequestFactory.setHttpClient(httpClientBuilder().build());
	clientHttpRequestFactory.setConnectTimeout(6000); // 连接超时,毫秒		
	clientHttpRequestFactory.setReadTimeout(6000); // 读写超时,毫秒		
	return clientHttpRequestFactory;
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持猪先飞。

原文出处:https://bigbird.blog.csdn.net/article/details/106861972

[!--infotagslink--]

相关文章

  • 解决springboot使用logback日志出现LOG_PATH_IS_UNDEFINED文件夹的问题

    这篇文章主要介绍了解决springboot使用logback日志出现LOG_PATH_IS_UNDEFINED文件夹的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-28
  • Redis连接池配置及初始化实现

    这篇文章主要介绍了Redis连接池配置及初始化实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-29
  • SpringBoot实现excel文件生成和下载

    这篇文章主要为大家详细介绍了SpringBoot实现excel文件生成和下载,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-02-09
  • 详解springBoot启动时找不到或无法加载主类解决办法

    这篇文章主要介绍了详解springBoot启动时找不到或无法加载主类解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-09-16
  • SpringBoot集成Redis实现消息队列的方法

    这篇文章主要介绍了SpringBoot集成Redis实现消息队列的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-10
  • 解决Springboot get请求是参数过长的情况

    这篇文章主要介绍了解决Springboot get请求是参数过长的情况,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-17
  • Spring Boot项目@RestController使用重定向redirect方式

    这篇文章主要介绍了Spring Boot项目@RestController使用重定向redirect方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-02
  • Springboot+TCP监听服务器搭建过程图解

    这篇文章主要介绍了Springboot+TCP监听服务器搭建过程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-10-28
  • springBoot 项目排除数据库启动方式

    这篇文章主要介绍了springBoot 项目排除数据库启动方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-10
  • 详解SpringBoot之访问静态资源(webapp...)

    这篇文章主要介绍了详解SpringBoot之访问静态资源(webapp...),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-09-14
  • SpringBoot接口接收json参数解析

    这篇文章主要介绍了SpringBoot接口接收json参数解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-19
  • springboot中使用@Transactional注解事物不生效的坑

    这篇文章主要介绍了springboot中使用@Transactional注解事物不生效的原因,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-26
  • springboot多模块包扫描问题的解决方法

    这篇文章主要介绍了springboot多模块包扫描问题的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-09-16
  • Springboot mybatis plus druid多数据源解决方案 dynamic-datasource的使用详解

    这篇文章主要介绍了Springboot mybatis plus druid多数据源解决方案 dynamic-datasource的使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-11-18
  • Springboot+MDC+traceId日志中打印唯一traceId

    本文主要介绍了Springboot+MDC+traceId日志中打印唯一traceId,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-10-17
  • Springboot实现多线程注入bean的工具类操作

    这篇文章主要介绍了Springboot实现多线程注入bean的工具类操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-27
  • SpringBoot部署到Linux读取resources下的文件及遇到的坑

    本文主要给大家介绍SpringBoot部署到Linux读取resources下的文件,在平时业务开发过程中,很多朋友在获取到文件内容乱码或者文件读取不到的问题,今天给大家分享小编遇到的坑及处理方案,感兴趣的朋友跟随小编一起看看吧...2021-06-21
  • 关于springboot中nacos动态路由的配置

    这篇文章主要介绍了springboot中nacos动态路由的配置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-11
  • SpringBoot高版本修改为低版本时测试类报错的解决方案

    这篇文章主要介绍了SpringBoot高版本修改为低版本时测试类报错的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-18
  • 解决Springboot整合shiro时静态资源被拦截的问题

    这篇文章主要介绍了解决Springboot整合shiro时静态资源被拦截的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-26