大量 ESTABLISHED 连接但进程已退出导致端口耗尽的残留 socket 清理

10次阅读

进程退出后未正确关闭TCP连接会导致内核残留ESTABLISHED状态socket,引发端口耗尽;可通过ss/netstat检测,优先重启服务、调整tcp_fin_timeout等参数清理,根本在于代码显式close和合理超时设置。

大量 ESTABLISHED 连接但进程已退出导致端口耗尽的残留 socket 清理

进程退出后,其建立的 TCP 连接若未正确关闭(如未调用 close() 或崩溃退出),可能在内核中残留为 ESTABLISHED 状态的 socket。这类 socket 无法被新进程复用对应端口,长期积累会导致“端口耗尽”,表现为 bind: Address already in use 或连接失败。

确认残留 ESTABLISHED 连接

使用 ssnetstat 查看当前处于 ESTABLISHED 但无对应进程的 socket:

  • ss -tunp state established | grep -v "pid=" —— 若输出为空,说明所有 ESTABLISHED 连接都关联着进程;若有输出,即存在无主 socket
  • ss -tun state established | wc -l 统计总数,结合 ps aux | wc -l 判断是否明显失衡
  • 检查特定端口:ss -tuln sport = :8080ss -tun dst :192.168.1.100:80

理解为何会残留(非 bug,是内核行为)

linux 内核不会主动回收已退出进程的 socket,只要该 socket 仍满足以下任一条件,就会保持 ESTABLISHED 状态:

  • 应用层未调用 close(),且文件描述符未被自动释放(例如子进程继承了 fd 但未关闭)
  • socket 被设置为 SO_LINGER 且 linger 时间非零,进程退出时内核等待发送缓冲区清空
  • 连接对端持续发包,本端 TCP 需维持状态以响应 ACK/RST
  • socket 被其他进程通过 SCM_RIGHTS 传递并持有(较罕见)

安全清理方法(不重启、不丢数据)

直接 kill 或强制回收 socket 风险高,推荐按优先级顺序操作:

  • 重启对应服务进程:最稳妥。确保服务支持优雅关闭(如 nginxnginx -s quitgo 程序监听 SIGTERM),让应用自行 close 所有 socket
  • 调整内核参数加速超时回收
    • echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout(默认 60s,缩短 TIME_WaiT 前置等待)
    • echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse(允许 TIME_WAIT socket 重用于新连接,对 ESTABLISHED 无直接影响但缓解端口压力)
    • echo 1 > /proc/sys/net/ipv4/tcp_abort_on_overflow(慎用!仅调试时开启,溢出时发 RST 终止异常连接)
  • 手动释放(仅限调试,生产环境避免): 使用 ss -K dport = :8080(需要 kernel ≥ 4.15 + ss ≥ 5.0.0)可主动向指定连接发送 RST,强制清除。注意这会中断活跃通信,仅用于确认是残留连接且对端可重连的场景。

预防措施(根治关键)

从代码和部署层面减少残留概率:

  • 应用中所有 socket 创建后,确保在生命周期结束时显式 close(),尤其注意异常分支、goroutine 泄漏、defer 未生效等场景
  • 设置合理的超时:读写超时(SetReadDeadline/SetWriteDeadline)、连接超时(net.DialTimeout)、keepalive(SetKeepAlive
  • 服务启动前检查端口占用:lsof -i :8080ss -tuln | grep :8080,避免重复绑定
  • 容器化部署时,在 preStop hook 中发送 SIGTERM 并等待几秒再终止,给应用留出关闭时间
text=ZqhQzanResources