业务方反映调用接口超时,但服务端监控并未发现5xx异常。为了解决这一问题,我们进行了请求超时模拟实验,以探究具体情况。
1. 使用OpenResty模拟长时间服务端响应
我们通过设置延迟5秒来模拟长耗时服务端响应。
在实际操作中,OpenResty服务端日志显示请求执行完成并返回200 OK,这也印证了业务方反馈和服务端监控中未出现5xx报错的情况。
2. Golang和.NET默认的HttpClient超时设置
.NET HttpClient默认超时设置为100秒;而Golang的net/http则没有默认值,强烈建议设置timeout,避免服务端慢响应导致客户端性能下降。
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
var a = HttpReqTimeout();
Console.WriteLine(a.Result);
}
static async Task<string> HttpReqTimeout()
{
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(1)
};
using (var hc = new HttpClient(handler))
{
hc.Timeout = TimeSpan.FromSeconds(3);
return await hc.GetStringAsync("http://localhost/reqtimeout");
}
}
在使用.NET运行请求时,客户端在3秒后超时并爆出异常,而服务端日志记录显示执行完成并返回200 OK。
3. 使用Wireshark抓包分析实质情况
我们通过Wireshark抓包发现,客户端请求超时只会影响客户端本身,服务端继续处理并响应请求。
从抓包过程看,可分为三阶段:
1. HttpClient请求,正常TCP三次握手+请求确认;
2. 客户端3秒后超时,发送FIN+ACK数据包,服务端确认收到客户端的FIN包;
3. 服务端5秒尝试响应给客户端,最终检测到客户端已关闭并释放资源。
我们在服务端可以通过设置监控请求耗时占比来实时监测请求超时情况。
4. 服务端感知客户端请求超时能力
默认情况下,当客户端请求超时时,服务端会继续执行之后的响应。然而,服务器是具备感知客户端请求取消能力的。
// 在控制器/服务获取到当前请求的上下文,通过token感知到客户端取消,
var cancellationToken = httpContext.RequestAborted;
await LongLoop(cancellationToken);
public Task LongLoop(CancellationToken token)
{
while(true)
{
if (token.IsCancellationRequested == true)
{
break;
}
//--- 长耗时循环
}
return Task.CompletedTask;
}
C#是通过CancellationToken感知客户端取消,之后服务端可以记录客户端请求超时(通常记录为408响应码)。
Func getHello(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-ctx.Done():
err := ctx.Err()
fmt.Println("Request cancelled:", err)
return
case <-time.After(5 * time.Second):
io.WriteString(w, "Hello, HTTP!\n")
return
}
}
Golang则通过request.Context获取客户端取消信号,类似于C#的实现。
本文记录了HttpClient客户端超时在双端的现象,服务端会继续响应,在服务端可能检测不到客户端认定的报错。通过实践经验,我们深知熟能生巧。