springboot restTemplate Connection Pool Consolidation Mode

  • 2021-11-30 00:10:35
  • OfStack

Directory springboot restTemplate Connection Pool Integration restTemplate Introduction apache httpclientRestTemplate Configuration Class RestTemplate Connection Pool Configuration Parameters Test RestTemplateRestTemplate with Connection Pool Configuration http Connection Pool

springboot restTemplate Connection Pool Consolidation

restTemplate

Using http connection pooling can reduce connection setup and release time and improve the performance of http requests. If the client has to establish a new connection with the server every time it requests, that is, 3 handshakes will be very time consuming. This paper introduces how to integrate http connection pool in Springboot. Based on restTemplate+httpclient.

Introducing apache httpclient


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

RestTemplate Configuration Class


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;
/**
 *  In actual development, you should avoid every time http Requests are instantiated httpclient
 * restTemplate Connections are reused by default , Guarantee restTemplate Just a single example 
 *  References: 
 * 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) {
        // If you do not use HttpClient Use the connection pool of restTemplate Default SimpleClientHttpRequestFactory, The bottom layer is based on HttpURLConnection
        if (!httpClientProperties.isUseHttpClientPool()) {
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(httpClientProperties.getConnectTimeout());
            factory.setReadTimeout(httpClientProperties.getReadTimeout());
            return factory;
        }
        //HttpClient4.3 Versions and above are not set manually HttpClientConnectionManager, Connection pooling is used by default 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 Connection Pool Configuration Parameters


public class HttpClientProperties {
    /**
     *  Whether to use httpclient Connection pool 
     */
    private boolean useHttpClientPool = false;
    /**
     *  Obtain from the connection pool 1 A connection Timeout of 
     */
    private int connectionRequestTimeout = 3000;
    /**
     *  Establish connection timeout 
     */
    private int connectTimeout = 3000;
    /**
     *  Timeout for reading returned data after connection is established 
     */
    private int readTimeout = 5000;
    /**
     *  The maximum number of connections in the connection pool, 0 Unlimited representation 
     */
    private int maxTotalConnect = 128;
    /**
     *  Maximum number of connections per route 
     */
    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

Testing RestTemplate with Connection Pool


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 {
    /**
     *  Free inquiry number attribution interface 
     */
    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(" Send a request: " + 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 + ", Attribution :" + 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);
    }
}

Test comparison shows that if ClientHttpRequestFactory is not set, resttemplate will use SimpleClientHttpRequestFactory by default, and the bottom layer is based on HttpURLConnection;; The performance of this method is not much different from that of httpComponentsClientHttpRequestFactory with connection pool manually, and the performance of connection pool based on httpclient has a slight advantage, which is not obvious.

Whether you use SimpleClientHttpRequestFactory, which is the default of restTemplate, or HttpComponentsClientHttpRequestFactory provided by httpclient, connection reuse will occur, that is, only the first request takes a long time, and subsequent requests reuse connections.

Using httpclient, you can set evictExpiredConnections and evictIdleConnections to clean up expired and idle connections regularly. The bottom layer is that one thread is opened to perform the cleanup task, so be careful not to instantiate httpclient-related instances multiple times, which will lead to the continuous creation of threads.

Matters needing attention

In actual development, avoid instantiating httpclient every http request

The restTemplate reuses connections by default, ensuring that the restTemplate singleton is

RestTemplate Configure http Connection Pool


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()); 
        //  Use  utf-8  Of the encoded set  conver  Replace the default  conver (Default  string conver  The encoding set of is "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); //  Maximum number of connections in connection pool   
	poolingConnectionManager.setDefaultMaxPerRoute(100); //  Concurrency per host 
	return poolingConnectionManager;
    }
    
    @Bean
    public HttpClientBuilder httpClientBuilder() {
	HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // Settings HTTP Connection Manager 
	httpClientBuilder.setConnectionManager(poolingConnectionManager());
	return httpClientBuilder;
    }
    
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory() { 
	HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
	clientHttpRequestFactory.setHttpClient(httpClientBuilder().build());
	clientHttpRequestFactory.setConnectTimeout(6000); //  Connection timeout, milliseconds 		
	clientHttpRequestFactory.setReadTimeout(6000); //  Read-write timeout, milliseconds 		
	return clientHttpRequestFactory;
    }
}

Related articles: