springboot restTemplate連接池整合
restTemplate
使用http連接池能夠減少連接建立與釋放的時間,提升http請求的性能。如果客戶端每次請求都要和服務端建立新的連接,即三次握手將會非常耗時。本文介紹如何在Springboot中集成http連接池;基于restTemplate+httpclient實現。
引入apache httpclient
1
2
3
4
5
|
< dependency > < groupId >org.apache.httpcomponents</ groupId > < artifactId >httpclient</ artifactId > < version >4.5.6</ version > </ dependency > |
RestTemplate配置類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
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連接池配置參數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
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
1
2
3
4
5
6
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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連接池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
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