首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >net/http 超时如何配置?避免慢连接拖垮服务

net/http 超时如何配置?避免慢连接拖垮服务

作者头像
技术圈
发布2026-06-29 13:32:30
发布2026-06-29 13:32:30
120
举报

HTTP 服务被拖垮,不一定来自高并发。更麻烦的情况是慢连接:客户端迟迟不发完请求头,上传接口一点点发送 body,下游接口卡住后响应写不出去。单个连接看起来流量很小,却会长期占用文件描述符、goroutine 和内存缓冲区。

Go 的 net/http 很容易启动服务,但默认配置不会替生产环境兜住所有超时边界。直接使用 http.ListenAndServe,就无法配置 ReadHeaderTimeoutReadTimeoutWriteTimeoutIdleTimeout。对公网服务来说,这等于把连接生命周期交给客户端决定。

慢连接危险在哪里

HTTP 连接从建立到关闭,会经历读取请求头、读取请求体、执行业务逻辑、写出响应、等待 keep-alive 复用等阶段。只要某个阶段没有边界,异常客户端就可能把连接长期挂住。

典型慢连接并不需要大流量。比如每隔几秒发送一个字节,让服务端一直等完整请求头;或者用极低速率上传 body,让连接长时间处于读取状态。当这类连接数量变多,正常请求也会因为文件描述符或 goroutine 被占满而失败。

所以超时配置不是简单地“调短一点”,而是要拆开连接生命周期:请求头要严格,普通请求体要有上限,响应写入不能无限等待,空闲 keep-alive 连接也要及时回收。

先显式创建 Server

生产服务应该显式创建 http.Server,不要只依赖 http.ListenAndServe 的快捷写法。

代码语言:javascript
复制
srv := &http.Server{
    Addr:              ":8080",
    Handler:           mux,
    ReadHeaderTimeout: 3 * time.Second,
    WriteTimeout:      10 * time.Second,
    IdleTimeout:       60 * time.Second,
    MaxHeaderBytes:    1 << 20,
}

ReadHeaderTimeout 限制请求头读取时间,可以防住慢速 header。WriteTimeout 限制响应写入时间,避免客户端接收太慢时拖住服务端。IdleTimeout 控制 keep-alive 连接等待下一次请求的时间。MaxHeaderBytes 限制请求行和 header 的大小,避免异常 header 消耗过多内存。

ReadHeaderTimeout 和 ReadTimeout 的区别

ReadHeaderTimeout 只覆盖请求头读取。请求头读完后,handler 可以自己决定如何读取 body。

ReadTimeout 覆盖整个请求读取过程,包括 header 和 body。对普通 JSON API 来说,它很实用;对大文件上传来说,短 ReadTimeout 可能误伤正常用户。

普通 JSON API 请求体通常不大,可以配置较短的 ReadTimeout,例如 5 到 10 秒。如果客户端长时间不发完 body,服务端就能尽快关闭连接。

上传接口则更适合只用 ReadHeaderTimeout 防慢请求头,再在 handler 内限制 body 大小。

代码语言:javascript
复制
func upload(w http.ResponseWriter, r *http.Request) {
    r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
    defer r.Body.Close()
    n, err := io.Copy(io.Discard, r.Body)
    fmt.Fprintf(w, "received=%d err=%v", n, err)
}

MaxBytesReader 解决的是“最多读多少”,不是“多久读完”。它能阻止超大 body 撑爆资源,但不能单独防御极低速传输。上传场景通常还需要网关层的请求体大小、请求总时长、最小传输速率等限制。

WriteTimeout 不是业务超时

WriteTimeout 保护的是响应写入阶段。它能避免客户端读取响应过慢,导致服务端一直卡在写 socket 上。但它不等于业务逻辑超时。

如果 handler 内部访问数据库、缓存或第三方接口,应该使用 context 控制业务链路。

代码语言:javascript
复制
func handler(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()
    data, err := query(ctx)
    if err != nil {
        http.Error(w, "timeout", http.StatusGatewayTimeout)
    } else {
        json.NewEncoder(w).Encode(data)
    }
}

这样下游调用可以感知取消信号。客户端断开连接时,r.Context() 也会取消,避免后台任务继续占用资源。

客户端也要有超时

服务端要防慢连接,客户端也不能被慢服务拖住。调用外部接口时,不建议长期使用没有超时的默认客户端。

代码语言:javascript
复制
client := &http.Client{Timeout: 3 * time.Second}
resp, err := client.Get("https://api.example.com")
if err == nil {
    defer resp.Body.Close()
}

Client.Timeout 覆盖从发起请求到读取响应 body 的整体过程,适合大多数短请求。如果要更细地控制连接建立、TLS 握手和等待响应头,可以继续配置 Transport,其中 ResponseHeaderTimeout 只限制等待响应头的时间,不包括读取响应 body。

推荐配置思路

普通 JSON API 可以从这组数值起步:请求头 2 到 5 秒,整个请求读取 5 到 10 秒,响应写入 5 到 15 秒,空闲连接 30 到 120 秒,header 大小限制在 1 MiB 左右。

上传接口不要只靠全局 ReadTimeout。更稳妥的组合是:较短的 ReadHeaderTimeout、明确的 MaxBytesReader、网关层限制、对象存储直传,以及业务层 context 超时。

流式响应、SSE、长轮询接口要谨慎配置 WriteTimeout。固定短写超时可能和长连接语义冲突,必要时可以为这类接口拆分独立 server 或独立端口。

写在最后

net/http 的几个超时字段对应不同阶段:ReadHeaderTimeout 防慢请求头,ReadTimeout 约束请求读取,WriteTimeout 控制响应写入,IdleTimeout 回收空闲 keep-alive 连接,业务超时交给 context

超时配置不是为了让请求更快,而是为了让失败更可控。只要连接生命周期有清晰边界,慢连接就很难悄悄拖垮整个服务。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 技术圈子 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 慢连接危险在哪里
  • 先显式创建 Server
  • ReadHeaderTimeout 和 ReadTimeout 的区别
  • WriteTimeout 不是业务超时
  • 客户端也要有超时
  • 推荐配置思路
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档