3

springboot(28)HTTP连接池

 2 years ago
source link: https://wakzz.cn/2019/06/12/springboot/(28)HTTP%E8%BF%9E%E6%8E%A5%E6%B1%A0/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

springboot(28)HTTP连接池

祈雨的博客
2019-06-12

服务间基于HTTP通信相对于grpc、dubbo之类的通信效率要低得多,一方面是后者的传输数据结构紧凑,使用了序列化和压缩;另一方面,后者使用了TCP连接池,而前者默认情况下每一次服务间的通信会创建一个新的HTTP请求,会产生不小的性能消耗,对于需要额外非对称加密的HTTPS请求,性能消耗更加严重。

默认情况下springboot的RestTemplate使用的org.springframework.http.client.SimpleClientHttpRequestFactory,即对每一次HTTP请求均新建一个新的TCP连接,请求结束后则关闭该连接。

RestTemplate restTemplate = new RestTemplate();
for(int i=0;i<100;i++) {
ResponseEntity<String> responseEntity = restTemplate.getForEntity("https://wakzz.cn", String.class);
System.out.println(responseEntity.getBody());
}

通过抓包如下图,每次的HTTP请求都会断开上次的TCP连接,然后重新3次握手新建一个TCP连接。

image

HttpComponentsClientHttpRequestFactory默认使用HTTP连接池PoolingHttpClientConnectionManager,其默认参数maxTotal为10即连接池最大HTTP连接数量为10,maxPerRoute为2即连接池中相同目标IP和端口号的HTTP连接数量最多为2。

HttpComponentsClientHttpRequestFactory httpClientFactory = new HttpComponentsClientHttpRequestFactory();
httpClientFactory.setConnectTimeout(2000);
httpClientFactory.setReadTimeout(10000);

RestTemplate restTemplate = new RestTemplate(httpClientFactory);
for(int i=0;i<100;i++) {
ResponseEntity<String> responseEntity = restTemplate.getForEntity("https://wakzz.cn", String.class);
System.out.println(responseEntity.getBody());
}

或者用户自定义PoolingHttpClientConnectionManager的参数:

PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
poolingConnectionManager.setMaxTotal(100);
poolingConnectionManager.setDefaultMaxPerRoute(10);

HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(poolingConnectionManager)
.build();

HttpComponentsClientHttpRequestFactory httpClientFactory = new HttpComponentsClientHttpRequestFactory();
httpClientFactory.setConnectTimeout(2000);
httpClientFactory.setReadTimeout(10000);
httpClientFactory.setHttpClient(httpClient);

RestTemplate restTemplate = new RestTemplate(httpClientFactory);
for(int i=0;i<100;i++) {
ResponseEntity<String> responseEntity = restTemplate.getForEntity("https://wakzz.cn", String.class);
System.out.println(responseEntity.getBody());
}

通过抓包如下图,每次的HTTP请求都复用的同一个TCP连接,无需在建立TCP连接上花费多余的性能消耗。

image

连接池连接状态:

image

HttpComponentsClientHttpRequestFactory缺省值情况下在org.apache.http.impl.client.HttpClientBuilder#build创建HTTP连接池:

final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactoryCopy)
.build(),
null,
null,
dnsResolver,
connTimeToLive,
connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS);
if (defaultSocketConfig != null) {
poolingmgr.setDefaultSocketConfig(defaultSocketConfig);
}
if (defaultConnectionConfig != null) {
poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig);
}
if (systemProperties) {
String s = System.getProperty("http.keepAlive", "true");
if ("true".equalsIgnoreCase(s)) {
s = System.getProperty("http.maxConnections", "5");
final int max = Integer.parseInt(s);
poolingmgr.setDefaultMaxPerRoute(max);
poolingmgr.setMaxTotal(2 * max);
}
}
if (maxConnTotal > 0) {
poolingmgr.setMaxTotal(maxConnTotal);
}
if (maxConnPerRoute > 0) {
poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute);
}

org.apache.http.impl.execchain.MainClientExec#execute从连接池获取HTTP连接:

final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);
if (execAware != null) {
if (execAware.isAborted()) {
connRequest.cancel();
throw new RequestAbortedException("Request aborted");
} else {
execAware.setCancellable(connRequest);
}
}

final RequestConfig config = context.getRequestConfig();

final HttpClientConnection managedConn;
try {
final int timeout = config.getConnectionRequestTimeout();
managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
} catch(final InterruptedException interrupted) {
Thread.currentThread().interrupt();
throw new RequestAbortedException("Request aborted", interrupted);
} catch(final ExecutionException ex) {
Throwable cause = ex.getCause();
if (cause == null) {
cause = ex;
}
throw new RequestAbortedException("Request execution failed", cause);
}

每次发起HTTP请求时通过路由route(目标IP和端口号)从连接池获取链接,默认timeout为0即从连接池无限等待获取连接不超时。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK