其实网上有大量讨论HTTP长连接的文章,而且Idle Timeout和KeepAlive Timeout都是HTTP协议上的事情,跟Vert.x本身没有太大关系,只不过最近在项目上遇到了一些问题,用到了Vert.x的HttpClient,就干脆总结一下,留给自己今后做参考。
在使用Vert.x的HttpClient的时候,可以使用HttpClientOptions
配置KeepAlive Timeout以及Idle Timeout的行为,本文将讨论在Vert.x HttpClient中Idle Timeout的设置、如何启用和禁用KeepAlive,以及如果启用KeepAlive,其超时设置(Timeout)对HTTP连接保活的影响。
需要注意的是,这是客户端的设置,服务端由于实现技术多样,这里不深入讨论,本文默认服务端已经开启了KeepAlive(事实上也是大多数HTTP服务器的默认行为)。
本文讨论的场景仅适用HTTP/1.1的情况,HTTP/1.0处理KeepAlive方式略有不同,HTTP/1.1默认支持KeepAlive,HTTP/1.0需要显式在header里加入Connection: keep-alive。
这里我们设定多个场景来测试不同情况下,整个系统的行为表现是什么。
HttpClientOptions
里使用setKeepAlive(false)
来禁用KeepAlive:
final HttpClientOptions options = new HttpClientOptions()
.setKeepAlive(false);
此时,Vert.x Http Client会在请求头中加入connection: close
,表示在完成一次HTTP request/response的流程后,服务端需要主动发起关闭连接请求:
Wireshark抓包分析:
connection: close
,所以服务端主动发起(FIN, ACK)数据包,表示服务端已关闭连接,客户端获得数据包后,返回ACK表示确认,然后也向服务端发出(FIN, ACK),表示客户端也关闭了连接,最后服务端返回ACK,表示确认HttpClientOptions
里使用setKeepAlive(false)
来禁用KeepAlive:
final HttpClientOptions options = new HttpClientOptions()
.setKeepAliveTimeout(15)
.setKeepAlive(true);
此时,Vert.x Http Client并不会发送connection: close
头,因为HTTP/1.1默认使用长连接,所以无需额外指定任何请求头。在KeepAlive超时后,会由客户端主动关闭HTTP连接。
Wireshark抓包分析:
如果服务端的KeepAlive Timeout大于HttpClient的KeepAlive Timeout,那么当一段时间内没有任何HTTP请求发出,在HttpClient KeepAlive首先超时前,HTTP连接可以一直被重用,直到HttpClient KeepAlive超时,由客户端发起关闭连接请求:
如果服务端的KeepAlive Timeout小于HttpClient的KeepAlive Timeout(比如服务端KeepAlive Timeout为10s),那么当一段时间内没有任何HTTP请求发出,服务端会首先发起关闭连接请求:
为了模拟这样的场景,在创建Vert.x HttpClient时,使用如下HttpClientOptions
:
final HttpClientOptions options = new HttpClientOptions()
.setIdleTimeout(5)
.setIdleTimeoutUnit(TimeUnit.SECONDS)
.setKeepAliveTimeout(20)
.setKeepAlive(true);
然后让服务端在返回HTTP Response的时候,先等待7秒钟:
[HttpGet(Name = "GetWeatherForecast")]
public async Task<IEnumerable<WeatherForecast>> Get()
{
await Task.Delay(7000);
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
Wireshark抓包分析:
因此,可以这样理解这两个设置:
IdleTimeout
设置得比较短,而 KeepAliveTimeout
设置得比较长,连接会因为空闲超时(Idle Timeout)而关闭。因此,当新的 HTTP 请求到来时,需要建立新的连接。IdleTimeout
设置得比较长,而 KeepAliveTimeout
设置得比较短,在 Keep-Alive 有效时间内,即使当前连接上有请求在等待响应,该连接仍然可以接收新的 HTTP 请求,直到 Keep-Alive 超时触发。在vert.x中,RequestOptions也有类似的setIdleTimeout的方法来设置Idle Timeout,但它的作用域仅限于当前的HTTP请求,而HttpClientOptions.setIdleTimeout作用域为整个应用程序全局。
仍然使用上面【场景二】的环境设定,只是让客户端每隔KeepAlive Timeout相同时间发送一次请求,会发现,多数情况下,请求可以成功,但有时候会发生错误。比如,如果服务端KeepAlive Timeout为10秒,客户端也是每隔10秒发送请求:
io.vertx.core.net.impl.ConnectionBase SEVERE: Connection reset
Connection: keep-alive
,默认禁用Connection: close
,默认启用min(ckt, skt)
时间段内如果没有新的HTTP请求,并且HTTP连接处于空闲状态,客户端会发起HTTP连接关闭min(ckt, skt)
时间段内如果没有新的HTTP请求,并且HTTP连接处于空闲状态,服务端会发起HTTP连接关闭两者是独立的设置,但在某些情况下可能会产生交互。例如,如果 IdleTimeout
设置的时间比 KeepAliveTimeout
短,那么连接可能会因为空闲超时而关闭,即使 Keep-Alive 允许更长时间的连接复用。
最佳实践(参考自ChatGPT)
KeepAliveTimeout
。IdleTimeout
。IdleTimeout
时,确保它足够长以完成请求处理,但不要过长以免浪费资源。KeepAliveTimeout
时,确保它能够有效地复用连接以提高性能。通过合理设置这两个超时值,可以优化连接的使用效率,同时避免不必要的资源占用。