You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang HTTP服务运行12小时后无响应,CLOSE_WAIT连接过多排查求助

问题背景

我们用Golang开发的HTTP Web服务(监听8000端口),持续运行约12小时后会停止响应HTTP请求,无法返回200响应。登录服务所在节点执行netstat命令后,发现myserver进程存在大量CLOSE_WAIT状态的TCP连接。问题发生后,本地curl 8000端口会挂起,无任何响应返回。

netstat命令输出如下:

# netstat -alpn | grep -i 8000     
tcp        0      0 127.0.0.1:60502         127.0.0.1:8000          TIME_WAIT   -                   
tcp6       1      0 :::8000                 :::*                    LISTEN      20987/myserver       
tcp6       1      0 100.10.10.2:8000    200.10.10.2:38436    CLOSE_WAIT  20987/myserver      
tcp6       0      1 100.10.10.2:8000    200.10.10.2:32800    LAST_ACK    -                   
tcp6      91      0 100.10.10.2:8000    200.10.10.2:42204    ESTABLISHED -                   
tcp6       0      0 100.10.10.2:8000    200.10.10.2:46250    CLOSE_WAIT  20987/myserver       
tcp6       0      0 100.10.10.2:8000    200.10.10.2:44042    ESTABLISHED 20987/myserver       
tcp6       0      1 100.10.10.2:8000    200.10.10.2:40876    LAST_ACK    -                   
tcp6       0      0 100.10.10.2:8000    200.10.10.2:32774    CLOSE_WAIT  20987/myserver       
tcp6       0      1 100.10.10.2:8000    200.10.10.2:32792    LAST_ACK    -                   
tcp6       0      1 100.10.10.2:8000    200.10.10.2:33088    LAST_ACK    -                   
tcp6      92      0 100.10.10.2:8000    200.10.10.2:39122    CLOSE_WAIT  20987/myserver       
tcp6       0      1 100.10.10.2:8000    200.10.10.2:32788    LAST_ACK    -                   
tcp6       0      0 100.10.10.2:8000    200.10.10.2:32768    CLOSE_WAIT  20987/myserver       
tcp6       0      1 100.10.10.2:8000    200.10.10.2:38262    LAST_ACK    -                   
tcp6       0      1 100.10.10.2:8000    200.10.10.2:32790    LAST_ACK    -                   
tcp6       0      0 100.10.10.2:8000    200.10.10.2:32772    CLOSE_WAIT  20987/myserver       
tcp6       0      1 100.10.10.2:8000    200.10.10.2:32776    LAST_ACK    -                   
tcp6       0      1 100.10.10.2:8000    200.10.10.2:34102    LAST_ACK    -                   
tcp6       0      1 100.10.10.2:8000    200.10.10.2:38872    LAST_ACK    -                   
tcp6       0      0 100.10.10.2:8000    200.10.10.2:32798    CLOSE_WAIT  20987/myserver       
tcp6       0      1 100.10.10.2:8000    200.10.10.2:32786    LAST_ACK    -                   
tcp6       0      0 100.10.10.2:8000    200.10.10.2:46248    CLOSE_WAIT  20987/myserver       
tcp6       0      0 100.10.10.2:8000    200.10.10.2:38430    CLOSE_WAIT  20987/myserver       
tcp6       0      0 100.10.10.2:8000    200.10.10.2:39092    CLOSE_WAIT  20987/myserver       
tcp6       0      1 100.10.10.2:8000    200.10.10.2:38866    LAST_ACK    -                   
tcp6       0      1 100.10.10.2:8000    200.10.10.2:34126    LAST_ACK    -                   
tcp6      91      0 100.10.10.2:8000    200.10.10.2:32768    ESTABLISHED 20987/myserver       
tcp6       0      1 100.10.10.2:8000    200.10.10.2:38246    LAST_ACK    -                   
tcp6       0      0 100.10.10.2:8000    200.10.10.2:45316    CLOSE_WAIT  20987/myserver       
tcp6       0      0 100.10.10.2:8000    200.10.10.2:45328    CLOSE_WAIT  20987/myserver       
tcp6       0      0 100.10.10.2:8000    200.10.10.2:32782    CLOSE_WAIT  20987/myserver       
tcp6       0      0 100.10.10.2:8000    200.10.10.2:32794    CLOSE_WAIT  20987/myserver       
tcp6       0      1 100.10.10.2:8000    200.10.10.2:32772    LAST_ACK    -                   
tcp6       0      1 100.10.10.2:8000    200.10.10.2:33078    LAST_ACK    -                   

服务核心代码如下:

mux := http.NewServeMux()
mux.HandleFunc("/", taskHandler)

// New method to start a customized server with timeouts
myServer := &http.Server{
    Addr:         ":8000",
    Handler:      mux,
    ReadTimeout:  3 * time.Second,
    WriteTimeout: 3 * time.Second,
}
// Here we will disable keep alives for the server connections
myServer.SetKeepAlivesEnabled(false)
if err = myServer.ListenAndServe(); err != nil {
    log.Fatal("server failed to start %v", err)
}

请求处理函数:

func taskHandler(w http.ResponseWriter, r *http.Request) {
        response := "200"
        value := getSomeDBValue()
        if value == "0" {
            log.Info("Response sent back -> %s", response)
            io.WriteString(w, response)
        }
    }

疑问:是否是服务程序未正确关闭TCP连接导致?希望得到问题原因及排查方向的建议。

问题原因分析
  1. CLOSE_WAIT连接堆积的直接原因:CLOSE_WAIT状态表示对方(客户端)已经主动关闭了TCP连接,但我方(服务端)尚未调用close()关闭连接。结合代码来看,问题出在taskHandler的逻辑漏洞:

    • 当getSomeDBValue()返回的结果不等于"0"时,处理函数没有向客户端返回任何响应,也没有调用w.WriteHeader()或结束请求的操作。
    • 虽然设置了WriteTimeout: 3 * time.Second,但Golang的WriteTimeout是从请求读取完成后开始计时,直到响应写入完成。如果处理函数没有触发任何写入操作,这个超时不会生效,连接会一直处于打开状态。
    • 当客户端超时后主动关闭连接,服务端就会进入CLOSE_WAIT状态,且因为代码没有正确结束请求,连接资源无法被释放,长期堆积后耗尽服务端的文件描述符,导致新请求无法被处理,最终服务挂起。
  2. 服务挂起的连锁反应:系统可用文件描述符被耗尽后,新的TCP连接请求无法被服务端接收,本地curl会因为无法建立连接或连接后无法被处理而挂起。

排查与修复建议

修复步骤

  • 补全处理函数的分支逻辑:确保无论getSomeDBValue()返回什么结果,都向客户端返回合法的HTTP响应,比如:
    func taskHandler(w http.ResponseWriter, r *http.Request) {
        response := "200"
        value := getSomeDBValue()
        if value == "0" {
            log.Info("Response sent back -> %s", response)
            io.WriteString(w, response)
        } else {
            // 返回合适的状态码,比如400或500,确保请求被正确结束
            http.Error(w, "invalid value", http.StatusBadRequest)
        }
    }
    
  • 添加请求超时兜底:除了ReadTimeout和WriteTimeout,建议设置IdleTimeout(即使禁用了keepalive,也能覆盖异常未关闭的连接),同时可以考虑设置MaxHeaderBytes限制请求头大小,增强服务稳定性:
    myServer := &http.Server{
        Addr:         ":8000",
        Handler:      mux,
        ReadTimeout:  3 * time.Second,
        WriteTimeout: 3 * time.Second,
        IdleTimeout:  5 * time.Second, // 兜底超时,确保空闲连接被关闭
        MaxHeaderBytes: 1 << 20,
    }
    

排查方向

  • 监控文件描述符使用情况:在服务运行期间,用lsof -p <pid> | wc -l或cat /proc/<pid>/limits查看进程的文件描述符消耗趋势,确认是否是CLOSE_WAIT连接耗尽了资源。
  • 日志补充:在taskHandler中添加全分支的日志输出,记录getSomeDBValue()的返回值,确认非"0"的场景是否频繁出现,导致连接堆积。
  • TCP连接状态监控:定时执行netstat -alpn | grep -i 8000 | awk '{print $6}' | sort | uniq -c,统计各状态连接的数量变化,验证修复后CLOSE_WAIT连接是否不再堆积。

内容的提问来源于stack exchange,提问作者Rohan Parulekar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 01:10:27