请求端都会使用http连接池来维护与服务端的连接,类似的还有jedis连接池、jdbc datasource。
配置并使用连接池
java系统参数http.maxConnections控制HttpURLConnection为每个地址可维护的空闲tcp连接个数, 建议设置为处理线程池的大小;
但是业务代码要读取并清理完tcp通道里的response数据,HttpURLConnection才会把tcp连接供下个请求复用。
注意事项:
Apache HttpClient除了像HttpURLConnection一样可以管理连接池,还能通过设置DefaultMaxPerRoute以限制对单个地址的最大并发连接数。
@Bean("httpClient")
@Primary
public CloseableHttpClient httpClientRequestConfig(HttpClientConnectionManager connManager) {
// 5秒内未获取空闲连接则直接失败;
// 建立连接时,3秒超时;
// 发起请求后,30秒未获取响应头则超时
RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectionRequestTimeout(5000)
.setConnectTimeout(3000).setSocketTimeout(30000).build();
HttpClientBuilder builder = HttpClientBuilder.create().setConnectionManager(connManager)
.setDefaultRequestConfig(defaultRequestConfig)
.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
long timeoutMillis = DefaultConnectionKeepAliveStrategy.INSTANCE.getKeepAliveDuration(response,
context);
// 空闲连接,20秒关闭(响应头Keep-Alive未提供timeout值时)
if (timeoutMillis < 0) {
return 20 * 1000;
}
return timeoutMillis;
}
});
return builder.build();
}
@Bean
@Primary
public RestTemplate restTemplate(@Qualifier("httpClient") CloseableHttpClient httpClient,
RestTemplateBuilder builder) {
// 使用springboot自动初始化的builder,已加载各种RestTemplateRequestCustomizer
return builder.requestFactory(() -> new HttpComponentsClientHttpRequestFactory(httpClient)).build();
}
注意事项:
http {
# nginx默认支持开启与请求端的长连接;
# 一个tcp长连接上处理了1000个请求后,nginx就会返回Connection: close,让客户端主动断开tcp连接
keepalive_requests 1000;
# 通知客户端,空闲连接继续保持60s再关闭(Keep-Alive: timeout=60);
# 如果客户端未按要求在60s时关闭空闲连接,nginx实际在90s后也会主动关闭此连接,把TIME_WAIT记录留在nginx节点上
keepalive_timeout 90s 60s;
# 要与请求端保持http1.1通讯,就不能关闭chunked机制;
# 否则nginx会在完成响应后主动关闭与请求端的tcp连接
chunked_transfer_encoding on;
}
注意事项:
http {
# 尝试与upstream服务器建立tcp连接的最大耗时,默认值60s极为不合理,影响nginx排除死机节点的速度
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 300s;
upstream myapp {
# nginx与upstream服务器之间的空闲连接最大数量(必须配置)
keepalive 10;
keepalive_requests 1024;
# 与upstream的空闲连接保持多久后,主动断开连接
keepalive_timeout 15s;
server 10.10.0.8:8080;
server 10.10.0.9:8080;
}
server {
listen 80 default;
location / {
proxy_pass http://myapp;
# 指定HTTP 1.1协议,并且Connection不为Close时,才会与upstream保持长连接
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_set_header Cookie $http_cookie;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For ${proxy_add_x_forwarded_for};
}
}
}
注意事项:
f5用作7层负载的时候,与后端的tcp连接,并没有使用连接池管理,而是与请求端的连接一一对应;
并且f5会保留并传递response header里的Connection、Keep-Alive等信息。
只要服务端、请求端都不主动发起FIN操作,那么F5就会保持两端空闲的tcp连接。
防火墙一般会有几个限制,影响tcp连接的建立或者保持:
注意事项:
springboot默认使用tomcat;
tomcat默认使用并保持长连接20秒
修改默认配置(演示代码里配置的参数实际是默认值):
@Configuration
public class HttpApplication implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers((connector) -> {
if (connector.getProtocolHandler() instanceof Http11NioProtocol) {
Http11NioProtocol protocolHandler = (Http11NioProtocol) connector.getProtocolHandler();
// 接收连接后,等待request header到达的时间
protocolHandler.setConnectionTimeout(20000);
protocolHandler.setUseKeepAliveResponseHeader(true);
protocolHandler.setKeepAliveTimeout(60000);
protocolHandler.setMaxKeepAliveRequests(100);
}
});
}
}
WAF/NGFW会限制单个请求IP在单位时间内发起tcp连接的数量,TPS高且采用短连接就会超过限制
成功开启tcp_tw_reuse(握手时两端还需都开启tcp_timestamps),则客户端能够复用本地TIME_WAIT表里存在超过1s的socket地址;
否则,如果可用请求端口与服务地址组成的socket已经全部存在于本地TIME_WAIT表里的话,客户端直接放弃建立新的socket。
比如k8s的nodePort进行转发时,需要把请求端地址和转发目标POD的地址进行记录;
如果大量进行短连接,那么作为中间转发节点的conntrack列表里,也会遗留大量TIME_WAIT记录。
# 查看NAT记录最大数、当前记录数
cat /proc/sys/net/netfilter/nf_conntrack_max && cat /proc/sys/net/netfilter/nf_conntrack_count
# 查看NAT明细
cat /proc/net/nf_conntrack
由服务端主动断开tcp连接的话,TIME_WAIT记录留在linux服务端1分钟;同ip的请求端如果使用相同的请求端口再次发起请求,连接请求有几率被服务端默默忽略(请求端SYN包里的ISN小于服务端TIME_WAIT记录的SN),引起请求端Connection timed out错误。尤其是位于F5后面的nginx,nginx收到的所有请求都来自同一个F5的源IP,更容易出现这种情况。
即使tcp协议规定了ISN是随机且递增的,但如果旧请求的平均数据上传速度超过250KB/s或者ISN重头开始计数,那么从同一请求端口发起的新请求还是会被服务端TIME_WAIT记录阻断(未成功开启PAWS机制的情况下):
RFC 793 [RFC0793] suggests that the choice of the ISN of a connection is not arbitrary, but aims to reduce the chances of a stale segment
from being accepted by a new incarnation of a previous connection.
RFC 793 [RFC0793] suggests the use of a global 32-bit ISN generator that is incremented by 1 roughly every 4 microseconds.
如果整个链路上所有节点都配置好了对http1.1的支持,那么tcp连接的断开都是由请求端进行,不会有服务端存在大量TIME_WAIT的情况。