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

18次阅读

存在无主 ESTABLISHED 连接:进程崩溃或未调用 close()退出时,内核不回收 socket,连接卡在 ESTABLISHED 状态且无 PID 关联;可用 ss -tunp state established | grep -v “pid=” 验证。

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

确认是否存在无主 ESTABLISHED 连接

进程崩溃或未调用 close() 就退出时,内核不会自动回收其 TCP socket,这些连接会卡在 ESTABLISHED 状态,且不关联任何 PID。最直接的验证方式是:

  • ss -tunp state established | grep -v "pid=" —— 若有输出,说明存在“孤儿连接”
  • ss -tun state established | wc -lps aux | wc -l 对比:连接数远大于进程数(比如 5000+ ESTABLISHED 但只有 200 个活跃进程),大概率就是残留问题
  • 检查具体 端口 是否被占:如服务监听 :8080 却 bind 失败,可用 ss -tuln sport = :8080 查看是否被残留 socket 绑定

优先终止关联进程(最安全)

如果 ss -tunp 能查到 PID,说明连接仍由某进程持有,只是该进程可能僵死、卡住或响应迟缓。此时应先尝试优雅终止:

  • kill -15 发送 SIGTERM,给应用机会清理资源
  • 若 10 秒后连接仍在,再用 kill -9 强杀 —— 内核会在进程彻底消亡后回收其所有 socket
  • 注意:不要只 kill 子进程而漏掉父进程(比如 systemd 服务下,systemctl restart xxx 比手动 kill 更可靠)

强制清除单条无主 ESTABLISHED 连接(kernel ≥ 4.15)

当确认连接已无对应进程(ss -tunp 不显示 pid),又不能重启整个服务时,可借助 ss -K 主动发 RST 包中断它:

  • 先定位目标:如想清掉连向 192.168.1.100:8080 的 ESTABLISHED 连接,执行:
    ss -K dst 192.168.1.100:8080
  • 支持更细粒度匹配,例如按本地端口:
    ss -K dport = :8080
  • ⚠️ 前提:系统内核 ≥ 4.15 且 iproute2 ≥ 5.0.0;否则会报错 Unknown option: K
  • 该操作不依赖进程状态,直接作用于内核 socket 表,但属于“外科手术”,生产环境慎用,避免误杀关键长连接

避免复发:代码和系统层双加固

清理只是救火,根源在程序没正确管理连接生命周期:

  • 应用层必须显式调用 close()(Go 的 defer conn.Close()、Python 的 with socket……、Java 的 try-with-resources
  • 设置合理的 socket 超时:setsockopt(SO_KEEPALIVE) + 调整 /proc/sys/net/ipv4/tcp_keepalive_time(默认 7200s 太长,建议设为 600–1800)
  • 避免短连接高频复用同一端口:客户端可启用 SO_REUSEADDR,服务端注意 TIME_WAIT 回收(net.ipv4.tcp_tw_reuse = 1
  • 监控项要加一条:ss -tun state established | wc -l 持续告警,阈值建议设为最大可用端口数的 60%

真正棘手的不是怎么删,而是删完一小时又涨回来——那说明你还没找到那个忘了 close()goroutine 或线程。

text=ZqhQzanResources