springboot restTemplate連接池整合方式
springboot restTemplate連接池整合
restTemplate
使用http連接池能夠減少連接建立與釋放的時間,提升http請求的性能。如果客戶端每次請求都要和服務(wù)端建立新的連接,即三次握手將會非常耗時。本文介紹如何在Springboot中集成http連接池;基于restTemplate+httpclient實現(xiàn)。
引入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;
/**
* 實際開發(fā)中要避免每次http請求都實例化httpclient
* restTemplate默認會復(fù)用連接,保證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及以上版本不手動設(shè)置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連接池配置參數(shù)
public class HttpClientProperties {
/**
* 是否使用httpclient連接池
*/
private boolean useHttpClientPool = false;
/**
* 從連接池中獲得一個connection的超時時間
*/
private int connectionRequestTimeout = 3000;
/**
* 建立連接超時時間
*/
private int connectTimeout = 3000;
/**
* 建立連接后讀取返回數(shù)據(jù)的超時時間
*/
private int readTimeout = 5000;
/**
* 連接池的最大連接數(shù),0代表不限
*/
private int maxTotalConnect = 128;
/**
* 每個路由的最大連接數(shù)
*/
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("發(fā)送請求:" + 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);
}
}
測試比較發(fā)現(xiàn),如果不設(shè)置ClientHttpRequestFactory,resttemplate默認會使用SimpleClientHttpRequestFactory,底層基于HttpURLConnection;這種方式和手動設(shè)置帶連接池的httpComponentsClientHttpRequestFactory性能差別不大,基于httpclient的連接池性能稍有優(yōu)勢,不是太明顯。
不管是使用restTemplate默認的SimpleClientHttpRequestFactory還是使用httpclient提供的HttpComponentsClientHttpRequestFactory,都會進行連接復(fù)用,即只有第一次請求耗時較高,后面的請求都復(fù)用連接。
使用httpclient可以設(shè)置evictExpiredConnections、evictIdleConnections進行定時清理過期、閑置連接。底層是開啟了一個線程去執(zhí)行清理任務(wù),因此注意不能多次實例化httpclient相關(guān)的實例,會導(dǎo)致不斷創(chuàng)建線程。
注意事項
實際開發(fā)中要避免每次http請求都實例化httpclient
restTemplate默認會復(fù)用連接,保證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); // 連接池最大連接數(shù)
poolingConnectionManager.setDefaultMaxPerRoute(100); // 每個主機的并發(fā)
return poolingConnectionManager;
}
@Bean
public HttpClientBuilder httpClientBuilder() {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
//設(shè)置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;
}
}
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java理論基礎(chǔ)Stream API終端操作示例解析
這篇文章主要為大家介紹了java理論基礎(chǔ)Stream API終端操作示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-03-03
淺析Java 常用的 4 種加密方式(MD5+Base64+SHA+BCrypt)
這篇文章主要介紹了Java 常用的 4 種加密方式(MD5+Base64+SHA+BCrypt),本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-10-10
SpringBoot中AOP的動態(tài)匹配和靜態(tài)匹配詳解
這篇文章主要介紹了SpringBoot中AOP的動態(tài)匹配和靜態(tài)匹配詳解,在創(chuàng)建代理的時候?qū)δ繕祟惖拿總€連接點使用靜態(tài)切點檢查,如果僅通過靜態(tài)切點檢查就可以知道連接點是不匹配的,則在運行時就不再進行動態(tài)檢查了,需要的朋友可以參考下2023-09-09
ibatis結(jié)合oracle批量插入三種方法的測評
今天小編就為大家分享一篇關(guān)于ibatis結(jié)合oracle批量插入三種方法的測評,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12
SpringBoot基于過濾器和內(nèi)存實現(xiàn)重復(fù)請求攔截功能
這篇文章主要介紹了SpringBoot基于過濾器和內(nèi)存實現(xiàn)重復(fù)請求攔截,這里我們使用過濾器的方式對進入服務(wù)器的請求進行過濾操作,實現(xiàn)對相同客戶端請求同一個接口的過濾,需要的朋友可以參考下2023-01-01
解決Java執(zhí)行Cmd命令出現(xiàn)的死鎖問題
這篇文章主要介紹了關(guān)于Java執(zhí)行Cmd命令出現(xiàn)的死鎖問題解決,解決方法就是在waitfor()方法之前讀出窗口的標準輸出、輸出、錯誤緩沖區(qū)中的內(nèi)容,本文給大家介紹的非常詳細,需要的朋友可以參考下2022-07-07

