写点什么

http 请求超时, 底层发生了什么?

  • 2024-12-06
    福建
  • 本文字数:2163 字

    阅读完需:约 7 分钟

业务方反应调用接口超时,但是在服务端监控并没有看到 5xx 异常, 于是我们模拟一下请求超时时发生了什么?


1.openresty 模拟长耗时服务端


延迟 5s 响应


error_log logs/error.log; http {    server {      listen 80;      charset  utf-8;            location /reqtimeout {      default_type text/html;      content_by_lua '      local  start  = os.clock()          while os.clock() - start  <  5 do  end            ngx.say("delay  success!!")      ';    }  }  }
复制代码


2.golang 和.net 默认的 httpclient 对外都只有一个 timeout 设置


用于控制请求、响应的整体时间


.net httpclient 默认 timeout= 100s;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");        }    }
复制代码


dotnet run ./ 显示客户端请求 3s 超时,爆出异常


Hello, World!Unhandled exception. System.AggregateException: One or more errors occurred. (A task was canceled.) ---> System.Threading.Tasks.TaskCanceledException: A task was canceled.   at System.Threading.Tasks.Task.GetExceptions(Boolean includeTaskCanceledExceptions)   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)   at ConsoleApp1.Program.Main(String[] args) in /Users/admin/RiderProjects/TestHttpClientFactory/ConsoleApp1/Program.cs:line 9--- End of stack trace from previous location ---    --- End of inner exception stack trace ---   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)   at ConsoleApp1.Program.Main(String[] args) in /Users/admin/RiderProjects/TestHttpClientFactory/ConsoleApp1/Program.cs:line 9
复制代码


openresty 服务端日志,显示执行完成,返回 200ok:


127.0.0.1 - - [04/Dec/2024:15:17:50 +0800] "GET /reqtimeout HTTP/1.1" 200 28 "-" "-"
复制代码


这也正是对应上了业务方的反馈和服务端的监控现象(无 5xx 报错)。


3.wireshark 抓包看实质


tcp.port == 80 && ip.addr ==127.0.0.1 && ip.dst ==127.0.0.1



从 tcp 抓包过程看,分为三阶段:


1>. httpclient 请求, 正常 tcp 三次握手+ 请求确认;

2>. 客户端 3s 之后超时, 客户端发送 FIN+ACK 数据包(客户端标记连接已经被关闭), 服务端确认收到客户端的 FIN 包;

3>. 服务端 5s 尝试响应给客户端,最终会检测到客户端已经关闭而释放资源。


也就是说客户端请求超时,只会影响客户端, 服务端还会继续处理并响应, 这也是我们在服务端监控上看不到 5xx 报错的原因,可以通过在服务端设置: request_time between (-xx, 3s) 监测请求耗时占比。

正常的请求/响应读者可以参考下图:



4. 服务端能感知到客户端请求超时吗 ?


客户端请求超时, 默认情况下服务端都是继续执行之后响应;


服务器是具备感知客户端请求取消的能力的。


C# 是通过CancellationToken,感知客户端取消,之后服务端可以做一些逻辑,比如记录客户端请求超时(常规实践是记录 408 响应码)


// 在控制器/服务获取到当前请求的上下文,通过token感知到客户端取消,var cancellationToken = httpContext.RequestAborted;await LongLoop(cancellationToken);  public Task LongLoop(CancellationToken  token){    while(true)    {        if  (token.IsCancellationRequested == true)        {            break;        }        //---  长耗时循环    }     return Task.CompletedTask;}
复制代码


golang 是通过 request.Context 获取客户端取消信号,内核类似于 C#


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        }}
复制代码


本文记录了 httpclient 客户端超时在双端的现象, 服务端会继续响应,在服务端可能检测不到客户端认定的报错, 经验无他,唯手熟尔。


文章转载自:码甲哥不卷

原文链接:https://www.cnblogs.com/JulianHuang/p/18586745

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
http请求超时, 底层发生了什么?_网络协议_不在线第一只蜗牛_InfoQ写作社区