springboot restTemplate Connection Pool Consolidation Mode
- 2021-11-30 00:10:35
- OfStack
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;
}
}