由于建立一个网络连接相对于本地操作十分昂贵
, 所以系统应该尽量重用一个已经建立的连接.
因此, 在http 1.0的规范中定义了 Keep-Alive
header, 一个典型的例子:
Keep-Alive: timeout=5, max=1000
这里的 timeout 表示如果没有后续的重用, 让这个连接最短逗留(idle) 5秒才能关闭它. 若有重用, 这从最后一个重用重新开始计时.
这里的 max 表示如果这个连接被一直重用, 但是这个连接最多只能发送1000个请求(或响应), 一旦超过这个数目, 即使能还没等idle 5秒, 也要关闭它. 所以它表示最多可以通过这个连接发送多少请求(或响应).
http 1.1
http 1.1 默认是持久化连接, 所以默认暗含是 Keep-Alive
的, 即便不发送这个头字段. 如果不在 http 的请求和响应中发送这个头字段, 那么连接的两端又该如何控制 idle 的timeout 和 一个连接最多可以发送的请求(或响应)的数目呢?
既然是为了重用, 那就最大化重用. 是不是一直重用某个连接, 一直不关闭就好了呢?
timeout 的合理性
- 从资源管理的角度. 若服务器端一直保持连接, 那么每个新用户都保持一个连接, 即便用户几分钟没数据传输, 这会导致服务器资源(连接, 内存等)很快被耗尽. 若客户端程序(浏览器, 代码客户端)也会有一样的问题.
- 从公平的角度. 若一个连接一直保持, 那么后来的新用户肯定无法再连接上.
- 从安全和性能的角度. 若保持很多没有数据的连接, 可能会有潜在的性能降级或者内存泄漏问题.
设置一个合理的 timeout 值, 保证一旦一段时间没数据传输, 就关闭这个连接.
max 请求的合理性
既然有了 timeout 可以保证 idle 了就关闭, 同时为了最大化重用, 那么是不是不需要 max 了呢?
考虑一种情况: timeout 是5秒, 但是每当 idle 4秒的时候, 就有一个新的请求(或响应), 那么这个连接将一直会被重用. 那么一直重用又有什么不好呢?
在理想的情况一下, 一直有数据固定频率的传输, 永久的使用这个连接其实是非常美好的一种状态.
但是 max 也有其它合理的地方:
- 避免某些写的不好的代码把某些请求(request)资源绑定在这个连接之上, 导致内存泄漏, 若关掉连接则全部释放.
- 公平性. 比如某些抢票的网站, 避免某些人一直先保持一个连接, 导致后面的人根本连不上. max 保证多少次请求之后, 一定关闭.
- 对于某些负载均衡器(Loader Balancer), 如果某个特定IP上一直保持特别多的连接, 会导致不再均衡.
服务端的 timeout 和 max
虽然 http 1.1 不再设置 Keep-Alive
头字段, 默认是持久连接. 但是连接的2端仍然要对合理的这2种机制做处理. 下面列举一些常见的服务端的处理和配置.
Tomcat web 服务器
Tomcat 是一个常用的 Java web 服务器. 在今天(20240605)最新的 10.1 版本里面的 http Connector 的配置里, 就能看到关于 timeout 和 max 的配置:
keepAliveTimeout
: 默认60秒.
maxKeepAliveRequests
: 默认100.
nginx 服务器
nginx 的配置分别是:
keepalive_timeout
: 默认75秒.
keepalive_requests
: 默认 1000.
不过, nginx 还有一个 keepalive_time
: 类似max, 它从时间角度约束一个连接从开始建立最长存活多久, 区别于idle timeout.
envoy proxy
对于 Envoy proxy 没有对应的完全一致的概念, 不过它有另外2个参数:
common_http_protocol_options.idle_timeout
:
stream_idle_timeout
:
更多内容参考: https://www.envoyproxy.io/docs/envoy/latest/faq/configuration/timeouts
和 https://github.com/envoyproxy/envoy/issues/8652
客户端的 timeout 和 max
既然对于 http 1.1 默认是持久的, 那么客户端也是暗含的, 不发送 Keep-Alive
的, 那么客户端是怎么处理的呢?
chrome 浏览器
没有找到相关文档.
Apache httpClient
在 4.5 版本 2.6 节 连接管理部份(https://hc.apache.org/httpcomponents-client-4.5.x/current/tutorial/html/connmgmt.html), 有下面一段描述:
“If the Keep-Alive header is not present in the response, HttpClient assumes the connection can be kept alive indefinitely”
JDK HttpClient
对于取值的大概处理:
https://github.com/openjdk/jdk/blob/e1870d360e05c372e672b519d7de2a60c333675b/src/java.base/share/classes/sun/net/www/http/HttpClient.java#L892-L937
max 的取值:
https://github.com/openjdk/jdk/blob/e1870d360e05c372e672b519d7de2a60c333675b/src/java.base/share/classes/sun/net/www/http/HttpClient.java#L902C25-L902C82
keepAliveConnections = p.findInt("max", usingProxy?50:5);
timeout 的取值
https://github.com/openjdk/jdk/blob/e1870d360e05c372e672b519d7de2a60c333675b/src/java.base/share/classes/sun/net/www/http/HttpClient.java#L906C37-L918
/*
* The timeout if specified by the server. Following values possible
* 0: the server specified no keep alive headers
* -1: the server provided "Connection: keep-alive" but did not specify a
* a particular time in a "Keep-Alive:" headers
* -2: the server provided "Connection: keep-alive" and timeout=0
* Positive values are the number of seconds specified by the server
* in a "Keep-Alive" header
*/
OptionalInt timeout = p.findInt("timeout");
if (timeout.isEmpty()) {
keepAliveTimeout = -1;
} else {
keepAliveTimeout = timeout.getAsInt();
if (keepAliveTimeout < 0) {
// if the server specified a negative (invalid) value
// then we set to -1, which is equivalent to no value
keepAliveTimeout = -1;
} else if (keepAliveTimeout == 0) {
// handled specially to mean close connection immediately
keepAliveTimeout = -2;
}
}
max 用完之后, 关闭连接:
https://github.com/openjdk/jdk/blob/e1870d360e05c372e672b519d7de2a60c333675b/src/java.base/share/classes/sun/net/www/http/HttpClient.java#L443-L453
客户端总结
客户端也会考虑 连接idle 的timeout 和 max 请求数, 但是很多情况下, 它没有服务端那么紧迫, 但是客户端也有这些机制, 只是没有那么透明.