ss 与 lsof 深入:TIME_WAIT、CLOSE_WAIT 与已删除还占磁盘的文件
ss -s 一看,几千个 TIME_WAIT 连接,要不要处理?删掉了几个 G 的大日志,df 显示磁盘却一点没释放,空间去哪了?这两个问题,一个考验对 ss 输出里 TCP 状态的理解,一个考验对 lsof “一切皆文件”哲学的运用——都超出了”查个端口占用”的基本盘。
这是 Linux 命令系列的一篇,承接 Linux 常见命令与工具地图。先一句话对齐两个工具是干什么的:ss(socket statistics)查看系统里所有网络连接的状态,是 netstat 的现代替代;lsof(list open files)列出进程打开的所有”文件”——Linux 里网络连接、管道也都算文件,所以它既能查端口也能查文件占用。两者查端口监听/占用的基本用法在 服务器排查那篇 讲过了,本篇不再重复,直接进深水区。
ss 按状态统计和过滤
ss(socket statistics)除了列连接,还能按 TCP 状态统计和过滤——排查连接类问题时,先看全局分布再钻具体状态:
1 | ss -s |
TIME_WAIT:正常现象,别急着动内核参数
TCP 连接关闭时,主动发起关闭的一方会进入 TIME_WAIT 状态,等待一段固定时间(2 倍 MSL,通常总共几十秒到几分钟)后自动消失——这是 TCP 协议设计的一部分,用来确保最后的确认包送达、避免旧连接的残留数据包串进新连接。
所以看到一批 TIME_WAIT 不用慌:它们不占进程资源(连接已经关了),会自己过期,是协议在正常工作。数量大到几千上万,说明的也不是故障,而是”短连接建立和拆除得太频繁”——比如服务间调用没用连接池、HTTP 客户端没开 keep-alive。这时候正确的方向是改用长连接/连接池,从源头减少连接的建拆次数,而不是上来就调内核参数硬压。
CLOSE_WAIT:这个才需要警惕
CLOSE_WAIT 的含义完全不同:对端已经关闭了连接,但本端的应用程序还没有调用 close() 把连接关掉。内核收到了对方的挥手、在等你的程序把连接收尾,你的程序却一直没动作——连接就卡在 CLOSE_WAIT。
零星几个是瞬时现象,但 CLOSE_WAIT 持续堆积基本可以断定是应用代码的 bug:某个分支漏了关闭连接(常见于异常路径——请求出错提前 return,忘了释放连接)。特征也很好认:
1 | ss -tn state close-wait | wc -l |
重启进程能把 CLOSE_WAIT 清零,但那只是止痛——代码不修,堆积会再来一遍。这是”监控指标能直接指向代码 bug”的少数场景之一,值得把 CLOSE_WAIT 数量放进告警。
lsof:一切皆文件的排查方式
Linux 里普通文件、目录、网络连接、管道全是”文件”,lsof(list open files)因此成了万能反查工具——从进程查文件,或从文件反查进程:
1 | lsof -p 12345 |
lsof +L1:已删除但还占着磁盘的文件
回收 ls/du/df 那篇 埋的伏笔——df 显示磁盘快满,du 把目录加起来却对不上,差的那块空间在哪?
要理解这个现象,先把三个东西分开:文件内容(磁盘上的数据块,真正占空间的是它)、目录里的文件名(指向内容的入口)、进程的文件句柄(进程打开文件后拿到的访问通道)。关键规则是:内容被释放的条件,是”没有任何名字指向它”且”没有任何进程打开着它”——两个条件要同时满足。
rm 只做了一件事:删掉目录里的名字。如果这时还有进程开着这个文件(典型场景:删了正在被服务写入的日志),第二个条件不满足,数据块就原地不动——于是出现了诡异的一幕:
du是沿着目录树把文件加起来统计的,名字没了,它就统计不到这份内容df统计的是文件系统层面数据块的实际占用,这份”无名内容”照样算在内
你的理解方向是对的:df 显示的确实还包含着”删除前”的那份空间——因为那份内容根本没被删掉,被删掉的只是名字。lsof +L1 就是把这些”有内容、有句柄、没名字”的文件找出来:
1 | lsof +L1 |
找到之后有两种处理,原理不同但殊途同归:
1 | systemctl restart myapp |
回答一个自然的疑问:方式二清空后句柄确实还开着,为什么空间能释放?因为占磁盘的是数据块,不是句柄——句柄只是访问通道,本身不占什么空间。内容被截断成 0 字节,数据块立刻归还给文件系统,df 马上就能看到下降;进程后续再写日志,从 0 开始占用新的块,服务全程不受影响。
预防比补救简单:要清空正在被写入的日志,用 truncate -s 0 文件 或 : > 文件 原地清空内容(名字和句柄都不动,只清数据块),别用 rm——rm 删的是名字、留下的恰恰是那份占空间的内容,方向完全反了。
常用命令速查表
| 命令 | 作用 |
|---|---|
ss -s |
按状态汇总统计所有连接 |
ss -tn state 状态名 |
只看指定状态的 TCP 连接 |
ss -tnp state close-wait |
查 CLOSE_WAIT 挂在哪个进程 |
| TIME_WAIT 大量出现 | 短连接太频繁,上连接池/keep-alive,通常不是故障 |
| CLOSE_WAIT 持续堆积 | 应用漏了 close(),修代码,重启只是止痛 |
lsof -p PID |
进程打开了哪些文件 |
lsof 文件路径 / +D 目录 |
反查谁在用这个文件/目录(umount busy 排查) |
lsof +L1 |
已删除但仍被占用的文件,df/du 对不上时查它 |
: > /proc/PID/fd/N |
不重启进程直接释放已删除文件的空间 |
| 清空活跃日志 | 用 truncate -s 0 或 : >,别用 rm |
写到这里
ss 的进阶在于读懂状态:TIME_WAIT 是协议正常运转(多了优化连接复用),CLOSE_WAIT 堆积是代码 bug 的直接证据。lsof 的进阶在于反向思维:从文件反查进程,+L1 专治”删了文件磁盘不释放”。这两个工具吃透,网络连接和文件句柄两类”隐形”问题就都有了抓手。








