生产环境半夜 12 点,同事在群里喊了一句:"端口又起不来了!"等你登上机器,ss -lntp 一敲,发现端口根本没被监听,可进程就是启动报 Address already in use。这时候大部分人的直觉是"谁占了我的端口",但真正的老运维会先问一句:"这个文件描述符到底还活着没有?"——而要回答这个问题,绕不开的就是 lsof。
lsof 全称 List Open Files,中文可以理解为"列出打开的文件"。在 Linux 的世界里,一切皆文件:普通文件、目录、socket 连接、管道、设备节点,全都以文件的形式存在。lsof 的作用就是把这些正在被进程使用的文件全部列出来,并按你的条件过滤。它不依赖任何 agent,不装任何插件,几乎是每个发行版自带的底牌之一,也是我从入行到现在用得最频繁的排查工具,没有之一。
这篇内容适合刚开始接触系统运维的同行,也适合那些已经会用 ps -ef | grep xxx 但面对"SOCKET 状态异常""磁盘明明有空间但写不进文件"这类问题时还不太顺手的朋友。我会把 lsof 的常用操作、输出解读、经典排障场景以及实际使用中踩过的坑一次讲透。
1. 为什么运维排障绕不开 lsof——"一切皆文件"的系统底层逻辑
先说一个我在带新人时经常被问到的问题:"查端口为什么不直接用 netstat 或 ss,非要学 lsof?"这个问题确实值得认真回答,因为它直接关系到你后面能不能用好 lsof。
1.1 netstat 和 ss 查不到的东西
netstat 和 ss 的核心能力是查看网络连接和监听的端口,它们的输出更侧重"这段连接现在是什么状态"。但"端口被占用"只是现象,背后真正的本质是"某个进程持有了一个 socket 文件描述符"。你如果只看到端口被占,想知道是哪个进程占了,ss -lntp 也能做到。可一旦场景换成"我的进程明明退出了,端口却还显示被占",或者"我有一个文件想删,系统却告诉我 device busy",netstat 基本就无能为力了——因为问题根本不在于网络,而在于文件描述符的持有情况。
lsof 的定位和它们不一样。lsof 站在"文件描述符"的视角看全局,它不管你关心的是网络还是磁盘,只要你给我一个条件——进程号、用户、目录、端口、文件——我就能把所有关联的打开的文件罗列出来。这个视角的统一性,正是它在排障中不可替代的原因。
1.2 Linux 中"打开的文件"到底指什么
很多新手会把"文件"理解成 Excel 或者日志文件,在 Linux 里这个定义要宽得多。一个进程在运行期间,会打开:
- 普通文件:配置、日志、数据文件
- 目录:进程当前工作目录、被挂载目录
- 网络 socket:TCP/UDP 连接、监听端口
- 管道和 FIFO:进程间通信用的通道
- 设备文件:
/dev/tty等字符设备 - Unix domain socket:本机进程间通信
lsof 会把所有这些都视为"文件"并纳入列表面。打个生活化的比方:你可以把进程想象成一个在图书馆里看书的读者,他手上同时抱着好几本书、几份报纸,还连着耳机。lsof 就是那个管理员,只要你问一句"谁在占用这本书"或者"张三现在手里拿着什么",它立刻能给你答案。你不需要知道这位读者坐哪一排、借了几本书,只要报上任一条件,它就能把所有关联信息翻出来。
1.3 lsof 相比其他工具的核心差异
很多生产环境的排障工具其实是一个"组合拳",但 lsof 的地位在于它能把不同维度串起来。拿一个最常见的场景举例:进程起不来,报端口被占。整个排查链路是这样的:
- 先用
ss -lntp或netstat -tlnp看谁在监听该端口——这能解决 80% 的端口冲突。 - 如果看不到监听,但端口还是被占,说明可能是一个没有 LISTEN 状态的 socket,或者是 TIME_WAIT 状态的连接残留。这时候
ss还能看,但有些边界情况它显示得不够直观。 - 更复杂的场景:某个进程 fork 出了子进程,父进程持有的 fd 被子进程继承,而父进程已经"伪装退出";又或者是一个已经被 kill 掉的进程,但它的 socket 还被另一个进程通过 fd 持有。
到了第 3 步,ss 和 netstat 基本就没办法给出完整的进程归属了,因为它们只输出网络协议栈层面的信息,而 lsof 能直接告诉你"这个 socket 对应的 inode 是被哪个 PID 用哪个 fd 打开的"。
所以我的建议很直接:ss 和 netstat 用来快速看连接状态,lsof 用来做深度定责。两个都会用才是完整能力,但如果你想先精通一个,选 lsof。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. lsof 基础三板斧:按进程、按端口、按用户快速定位
lsof 的选项看起来多,但日常运维真正高频的其实就那么几个组合。我习惯把它拆成三个维度:进程维度、端口维度、用户和文件维度。掌握这三个维度的命令,你就能覆盖绝大多数排查场景。
2.1 进程维度:-p 与不加参数的默认行为
不加任何参数直接执行 lsof,会输出当前系统所有进程打开的所有文件。这个输出在服务器上可能几十万行,平时没人这么干,但你要理解它的输出结构,因为后续所有过滤都是在这个全量列表上做裁剪。
bash复制# 查看某个 PID 打开的所有文件
lsof -p 1234
# 组合 -p 和 -d 查看特定文件描述符
lsof -p 1234 -d 1,2,3
-d 这个参数经常被忽略,但实际很有用。当你怀疑某个进程的文件描述符泄漏时,可以通过 -d 限定范围看前几个 fd:0 是标准输入,1 是标准输出,2 是标准错误。如果进程开了几千个 fd,你想看它是否把日志文件重复打开了多次,可以这样:
bash复制# 只看某个进程打开的普通日志文件,排除 socket 和管道
lsof -p 1234 | grep /var/log
在进程维度上,lsof 还有一个特别好的搭档:ps。我是这样配合的——先用 ps -ef | grep 关键词 定位到进程号,再用 lsof -p PID 看它的详细行为。比如 Java 应用突然磁盘 IO 飙升,先找到 PID,再看它打开了哪些日志文件、哪些数据文件,很快就能锁定是不是某个日志在疯狂滚动。
2.2 端口维度:-i 的用法与网卡筛选
查端口冲突是我用 lsof 最高频的场景。-i 选项支持非常灵活的格式,我列几个常用写法:
bash复制# 查看所有 TCP 和 UDP 网络连接
lsof -i
# 只看监听状态的端口(等价于 lsof -iTCP -sTCP:LISTEN)
lsof -iTCP -sTCP:LISTEN
# 查看某个具体端口
lsof -i :8080
# 查看某个端口范围
lsof -i :8000-9000
# 查看指向某个 IP 的连接
lsof -i @192.168.1.100
# 组合 IP 和端口
lsof -i @192.168.1.100:3306
lsof -i :8080 大概是所有命令里我敲得最多次的一条。它比 netstat -tlnp | grep 8080 少了一次管道过滤,而且输出信息更全——不仅能看到监听进程,还能看到所有已经建立连接的 PID。
有人可能注意到 -sTCP:LISTEN 这种写法。-s 是用来按协议状态过滤的,这里的 TCP:LISTEN 表示只显示 TCP 协议且状态为 LISTEN 的记录。如果你记不住这个语法,可以直接用 lsof -iTCP -sTCP:LISTEN 或者干脆 lsof -i :8080 | grep LISTEN,效果一样。我自己更习惯用 -sTCP:LISTEN,因为一行命令不用再二次过滤,在批量排查多端口时干净很多。
另外一个细节:-i 默认会同时匹配 IPv4 和 IPv6。所以你在看 lsof -i :8080 时,可能会看到两条记录,一条 IPv4、一条 IPv6,这往往让新手误以为有两个进程占用了端口。实际上这可能是同一个进程同时监听了 IPv4 和 IPv6 的 8080 端口,属于正常现象。
2.3 用户维度:-u 与文件维度:按路径反查
按用户过滤在生产环境很有用,特别是排查"某个业务账号是否遗留了残留进程"或者"这个用户是不是在异常读写文件":
bash复制# 查看某个用户打开的所有文件
lsof -u nginx
# 排除某个用户,常用于全量排查时排除自己
lsof -u ^root
# 组合用户和端口
lsof -u www-data -i :80
排除用户这个场景我经常用。有人误以为 lsof 的输出内容太多是"工具不好用",其实是因为没用好过滤条件。-u ^root 的写法是"排除 root",看非 root 用户的文件打开情况,做安全巡检时这一招特别快。
按文件路径反查是谁在占用,这是另一个让我在同事面前"封神"的操作:
bash复制# 查看某个文件正被哪些进程使用
lsof /path/to/file
# 查看某个目录下所有被打开的文件
lsof +D /var/log
lsof /var/log/syslog 这个命令在"想删除日志文件但删不掉"的场景里就是终极答案。你可能遇到过这种情况:磁盘空间明明被占满了,但 du -sh 看半天也没找到大文件,最后发现是一个进程把已经删除的文件持有着,真正的空间根本没释放。这个时候用 lsof +L1 就能直接列出所有"删除但仍被打开"的文件,这条命令我在后面会专门讲,这里先记住它是解决磁盘空间诡异消失的钥匙。
2.4 组合使用:让命令真正可落地
这几个维度不是孤立的,实际排障时我更倾向于组合使用。举一个我在线上排查问题时的完整链条:
bash复制# 1. 先确认进程在不在
ps -ef | grep payment-service
# 2. 找到 PID 后,看这个进程的网络连接情况
lsof -p 28765 -i
# 3. 进一步缩小范围,只看 8080 端口的连接
lsof -p 28765 -i :8080
# 4. 发现连接数异常多,统计一下连接状态分布
lsof -p 28765 -i :8080 | awk '{print $10}' | sort | uniq -c
第 4 步的 awk 统计看起来土,但在临时排查时特别好使,不用装额外工具,一台最小化的服务器上只要有的命令都能用。
3. 生产环境三大经典故障实战:lsof 怎么一击必杀
工具背得再熟,不会用等于零。我挑三个自己在真实生产环境里踩过的场景,完整还原排查链路,你看完可以直接把思路搬到自己服务器上。
3.1 场景一:端口明明没监听,却提示 Address already in use
这是最经典的"诡异问题"。某天应用部署报错,说 8080 端口被占用,但 ss -lntp 看了一圈,没有任何进程在监听 8080。第一反应是"系统出 bug 了"?其实不是。
我们用 lsof 看整个端口关联的所有 socket,包括非监听状态:
bash复制lsof -i :8080
输出中可能有一行不是 LISTEN,而是 ESTABLISHED 或 TIME_WAIT:
code复制COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 25123 root 36u IPv4 456789 0t0 TCP 192.168.1.20:8080->192.168.1.30:52340 (ESTABLISHED)
看到这行你就明白了,问题不是"新进程起不来",而是某个旧进程还保持着一个活跃连接。在容器环境或微服务架构里,这种情况尤其常见:服务 A 调用服务 B 的 8080 端口,B 已经重启,但那一次调用建立的 TCP 连接由于对端进程异常退出没收到 FIN,残留成了僵尸连接。
更细致的排查是连状态都过滤出来看:
bash复制lsof -i :8080 -sTCP:ESTABLISHED
lsof -i :8080 -sTCP:TIME_WAIT
修复手段通常是找到对应 PID,确认可以杀掉后 kill 掉。但这里我要提醒一句:如果一个端口上有大量 TIME_WAIT 连接,其实不一定要杀进程,因为这些连接通常会在 60 秒内自动清理。真正需要考虑的是一大堆 ESTABLISHED 指向同一台内网机器但没有相关业务流量,这种情况大多是负载均衡的健康检查连接或者监控探活连接,杀进程前一定要沟通清楚。
3.2 场景二:磁盘空间满了,但找不到大文件
这个场景几乎每个人都遇到过。df -h 显示 / 分区 100%,但 du -sh / 扫下来只有 70G,剩下 30G 不知道去哪了。
问题出在"文件被删除但进程仍持有"。一个进程打开了一个大文件,然后你把文件删了(比如误删了 log),但进程还一直往里写。Linux 的文件删除只是断开了目录项到 inode 的链接,只要还有进程持有该文件描述符,inode 和磁盘块就不会释放,df 里看到的空间始终是占用状态,但 du 按目录统计时根本找不到这个文件,因为目录项已经没了。
排查命令:
bash复制lsof +L1
+L1 的含义是列出 link count 小于 1 的文件,也就是"当前没有目录项引用、但依然被进程打开"的文件。这条命令会直接输出类似:
code复制COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 25123 root 34w REG 253,0 3224060928 123456 /usr/local/apps/logs/app.log (deleted)
看到 (deleted) 标记和 3G 的 SIZE/OFF,问题就锁定了一半。对应处理办法有两个方向:
- 如果进程不能重启,找到写日志的路径,通过
/proc/PID/fd/34这个软链接把内容 copy 出来备份,确认无用后> /proc/PID/fd/34清空文件,而不是删掉它。 - 如果进程可以重启,直接重启服务,让它释放 fd,空间会立刻回来。
这里有个很容易被忽略的坑:SIZE/OFF 显示的是文件当前大小,有的版本在文件被删除后这一列不一定实时刷新。如果文件还在持续增长而你看它 SIZE 没变,别慌,用 ls -l /proc/PID/fd/34 看真实大小。
还有一个实践经验:线上日志框架(比如 log4j 的 DailyRollingFileAppender、Python logging 的 TimedRotatingFileHandler)在做日志轮转时,如果配置不对,很容易把"旧文件"删掉而不是"重命名",导致进程一直持有被删除的 inode。这种场景下你会看到 df -h 使用率缓慢增长,但 /var/log 下没有任何大文件。这类问题表面看是 lsof 排查,实际上根源是对日志轮转机制的理解。lsof 能帮你定位,但要根治还得改日志轮转配置。
3.3 场景三:umount 提示 device busy,卸载不掉磁盘
需要扩容、修复磁盘、或者要卸载一个挂载点时,你敲 umount /data,系统提示 target is busy。这个错误意味着还有进程在使用 /data 目录下的某个文件或目录,内核出于安全考虑拒绝卸载。
以前很多人的做法是用 fuser -km /data 直接把相关进程全部 kill 掉,这确实快,但太粗暴了——如果有业务在正常读写数据,你这一刀下去会伤及无辜。更稳妥的做法是先用 lsof 看是谁在占用:
bash复制lsof /data
输出会显示所有打开了 /data 目录下文件的进程,比如:
code复制COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 25123 root cwd DIR 253,0 4096 12345 /data/app
nginx 25188 root mem REG 253,0 1M 54321 /data/log/nginx/error.log
第一行 cwd 表示这个进程的当前工作目录在 /data 下,这个特别容易忽略——你甚至都不需要打开任何文件,只要进程的工作目录在这个挂载点上,umount 就会失败。处理方式是让业务方把工作目录切到别处,或者用 lsof +D /data 把所有关联文件列全后逐个确认。
排查完成后,如果确认这些进程都没问题了,再用 umount /data 卸载,基本就能顺利执行。如果还是失败,可以查一下是否有人把共享库放在 /data 下,进程通过 mem 类型加载了这些动态库,这种情况下需要先停进程再卸载。
4. lsof 输出字段深度解读:看懂每一列,才能避免误判
很多人在网上复制了 lsof 命令,跑出来一堆结果,但完全看不懂每列是什么意思,于是只能靠 grep 硬猜。这里花点时间把输出结构讲透,你看懂一列,以后输出的信息才有价值。
4.1 十个核心字段及其含义
lsof 默认输出大致长这样:
code复制COMMAND PID TID TASKCMD USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 25123 - - root cwd DIR 253,0 4096 12 /data/app
java 25123 - - root 21u IPv4 123456 0t0 TCP 192.168.1.20:8080->192.168.1.30:52340 (ESTABLISHED)
各字段的含义:
| 字段 | 含义 | 排障时怎么看 |
|---|---|---|
| COMMAND | 进程对应的命令名 | 快速判断是什么程序,注意可能是启动脚本名 |
| PID | 进程号 | 后续 kill 或查 /proc/PID 的依据 |
| USER | 运行进程的用户 | 判断是业务账号还是系统账号 |
| FD | 文件描述符编号及模式 | cwd 是当前目录,mem 是内存映射,txt 是代码段,r 读、w 写、u 读写 |
| TYPE | 文件类型 | REG 普通文件,DIR 目录,IPv4/IPv6 网络 socket,unix domain socket,CHR 字符设备,FIFO 管道 |
| DEVICE | 设备号 | 磁盘文件对应的设备号,多个分区据此区分 |
| SIZE/OFF | 文件大小或偏移量 | 对普通文件是大小,对 socket 一般是 0t0 |
| NODE | inode 号 | 对网络 socket 显示的是内核 socket inode |
| NAME | 文件路径或连接信息 | 这是最核心的字段,通常排障信息都在这列 |
有一个细节值得展开:FD 列里写的 21u,数字 21 是文件描述符编号,u 是打开模式。如果你看到数字 + w,表示这个 fd 是以写模式打开的;r 表示只读;u 表示读写。对于日志文件来说,如果某个进程以 w 模式打开了一个日志文件,它大概率是日志的写者,文件轮转、空文件占空间之类的问题都要从这些 w 记录入手。
4.2 状态列:不是每一行都叫 LISTEN
在 NAME 列里,网络 socket 的末尾通常会有一个括号标注状态,比如 (LISTEN)、(ESTABLISHED)、(TIME_WAIT)、(CLOSE_WAIT)。我在实际带人时发现,很多人只关心 LISTEN,看到 ESTABLISHED 就觉得没用。这种理解在排查端口冲突场景下是不够的——正如前面说的,真正导致 Address already in use 的有时不是 LISTEN 而是 TIME_WAIT 或 ESTABLISHED。
这里要注意一个 lsof 的行为:默认 -i 会输出所有 TCP、UDP 记录,包括客户端主动发起的出站连接。所以在 lsof -i :3306 的输出中,你可能会看到一个进程连接到了 3306 端口,但它本身不是监听方,而是 MySQL 的客户端。这对排查"为什么连不上 MySQL"也有帮助,因为它能告诉你当前有哪些进程正在使用数据库连接。
4.3 用 -n -P 让输出更快更干净
这里有个很实在的优化建议:lsof 解析网络地址时,默认会做 DNS 反解和端口名到服务名的转换。在线上执行时,DNS 反解如果在内网环境超时,命令会明显卡顿。所以我的习惯是带上两个选项:
bash复制lsof -nP -i :8080
-n:不做主机名解析,直接显示 IP 地址-P:不做端口名解析,直接显示端口号,而不是显示 8080 为http-alt
这两个选项不是万能的,在内网环境尤其推荐加。没有它们你可能看到的是 localhost:http-alt 而不是 127.0.0.1:8080,信息没变,但解析过程白白浪费了时间。在生产环境排查争分夺秒时,每一条命令都干净利落很重要。
4.4 文件描述符泄漏的判定方法
文件描述符泄漏是 Java/C++ 服务最常见的稳定性隐患之一。现象是进程内存不高,但句柄数量一路飙升,最后 too many open files。
lsof 用来定位 fd 泄漏非常直观:
bash复制# 统计某个进程打开的 fd 总数
lsof -p PID | wc -l
# 更准确的做法是只统计 FD 列是数字的那些行
lsof -p PID | awk '$4 ~ /^[0-9]/' | wc -l
如果发现数量异常大,继续按类型分组:
bash复制lsof -p PID | awk '{print $5}' | sort | uniq -c | sort -rn
输出会告诉你:这个进程打开的大部分是 TCP 连接还是 REG 普通文件还是 FIFO 管道。如果是 TCP 连接最多,结合 lsof -p PID -i | awk '{print $10}' | sort | uniq -c 看连接状态分布,能判断是不是连接没有关闭;如果是普通文件最多,再结合 NAME 列看具体路径,很可能是日志写得太频繁或者临时文件没清理。
这里我要特别提一点:lsof -p PID \| wc -l 的数字会比你通过 /proc/PID/fd 统计出来的 fd 数量大,因为 lsof 默认输出里除了 fd 行还有 cwd、txt、mem 等内存映射行。要精确对比是否触及 ulimit -n 的硬限制,最可靠的是:
bash复制ls -l /proc/PID/fd | wc -l
这条命令只统计 /proc 里的实际 fd 数量。两者配合使用的意义在于:如果 /proc/PID/fd 数量已经接近 ulimit -n 而进程还在运行,说明泄漏已经很严重了;如果差异很大,可能只是 lsof 的输出包含了很多 map 文件,不是泄漏。
5. 爱恨交织的细节:lsof 的坑、边界和性能注意事项
工具用多了就会发现,每个命令都有它的脾气。lsof 不是万能的,我用它踩过的坑和总结的经验,这里一次说清楚。
5.1 权限:为什么明明运行了 lsof 却看不到别人的进程
如果你用普通用户执行 lsof,输出的内容只会是自己能访问的进程信息。这是因为 lsof 读取 /proc 目录下的进程信息时受到 Linux 权限模型限制。看别人的进程文件描述符,需要 root 权限或者至少对目标进程有 ptrace 权限。
所以生产环境排障,别用业务账号去跑 lsof,否则你很可能得到这样的结果:
code复制lsof: no pwd entry for uid 1001
或者直接什么都看不到。遇到这种情况,第一反应应该是 sudo -i 或 sudo lsof ...。权限问题在容器环境里更隐蔽:容器内跑 lsof 时,由于容器的 PID namespace 隔离,你只能看到容器内的进程,看不到宿主机上的其他进程。跨容器排查时,要在宿主机上跑 lsof 并用 -P 加上 grep 容器PID 去定位。
5.2 性能:大系统上跑 lsof 会卡,怎么优化
lsof 的工作机制是扫描 /proc 下所有进程的文件描述符。如果你的服务器有上千个进程、几十万个 fd,直接 lsof 全量扫描确实会有点慢,这符合预期,不用慌。但如果在高负载生产环境执行,还是要注意几点:
- 尽量缩小范围:能指定 PID 就不要全量;能指定端口就不要只指定协议;能指定文件路径就不要用
+D递归扫描整个目录。 - 用
-nP避免 DNS 反解,这一点前面说过,内网机器尤其重要。 - 避免在极端高负载时刻全量执行,如果一定要执行,用
timeout 30 lsof包一层超时保护,防止命令卡住影响你的操作窗口。
+D 这个递归扫描目录的选项也要注意,它在目录层次深、文件多的路径下开销很大。我一般先用 lsof 路径/具体文件 定位,确定文件路径后再做精准排查,很少直接对 /var/log 整个目录递归。
5.3 已删除文件的空间计算:你需要 -s 还是 -a?
前面提到的 +L1 能列出已删除但被打开的文件,但实际使用时我发现还有一个值得掌握的技巧:注意 lsof 默认是对多个条件取"或"关系,不是"与"关系。比如你执行:
bash复制lsof -p 1234 -i :8080
这表示"pid=1234 的所有文件,加上所有 8080 端口的连接",而不是"pid=1234 的 8080 端口连接"。想要取交集,需要加 -a:
bash复制lsof -a -p 1234 -i :8080
这个 -a 我早期经常忘记,导致输出里混入了大量无关进程,白折腾半天。怎么记呢:-a 就是 all conditions must be met,所有条件必须同时满足。没有它,lsof 默认每个条件是一个独立集合,最后做并集输出。对于需要精确限定的场景(进程 + 端口、用户 + 端口、进程 + 文件路径),一定要带上 -a。
比如常见的场景:确定 nginx 这个用户监听了哪些端口,不加 -a 会把所有 nginx 打开的文件全都列出来,加上 -a 之后输出就清爽了:
bash复制lsof -a -u nginx -i
5.4 一个容易误会的字段:SIZE/OFF 对 socket 无意义
有人会盯着 socket 行的 SIZE/OFF 列发懵,那一列几乎永远是 0t0。原因很简单:socket 不是一个有固定大小的文件对象,它没有普通文件那样的文件偏移量,所以内核把它置为 0。真正的连接数据量要看网络层统计,比如 ss -i 里的 bytes_sent 和 bytes_received。
这意味着:用 lsof 排查网络问题时,关注重点是 NAME 列里的 IP、端口和状态,而不是 SIZE 列。我见过有人写脚本去解析 socket 行的 SIZE/OFF 做监控,这个方向一开始就错了。
5.5 配合其他命令的完整排障姿势
lsof 虽强,但它不是终点。我最后总结一下自己的完整排障姿势,供你参考。
以"服务响应慢"为例子,我会这样展开:
bash复制# 1. 找到嫌疑进程
ps -ef | grep payment-service
# 2. 看这个进程打开了哪些文件,先排除日志刷爆的问题
lsof -p PID | grep -E 'REG|log' | head -20
# 3. 看看网络连接是否异常堆积
lsof -a -p PID -i | awk '{print $10}' | sort | uniq -c | sort -rn
# 4. 结合 ss 看系统层面连接状态
ss -s
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn
# 5. 如果发现 TIME_WAIT 或 CLOSE_WAIT 异常,再去追具体连接
ss -tanp | grep CLOSE_WAIT | head -20
第 2 步的 grep -E 'REG|log' 是为了快速过滤出普通文件和日志文件,避开干扰的 socket 行。第 3 步用了 -a 确保是"这个进程 + 网络连接"的交集,不会混入其他进程。第 4 步开始从 lsof 切换到 ss,因为要看系统整体连接数分布,lsof 的全量输出在这个场景下不如内核自己的统计准。
我常跟团队说一句话:lsof 管"是谁在用",ss 管"连接到什么状态",proc 文件系统管"实际数字是多少"。三样配合,几乎不会有个案查不出来。
回到文章开头说到的"端口明明没监听却起不来"的问题,你现在再想一遍这个流程:lsof -i :8080 看是否有非 LISTEN 状态的残留 socket;确认 PID 后 ps -fp PID 看进程是否存在;如果进程已不存在,但 socket 还在,检查是否被其他进程继承或处于 TIME_WAIT;最后根据实际情况决定是 kill 残留进程还是调整内核参数。整个过程五分钟内就能完成。
我在实际运维里的体会是,lsof 这种老牌工具的真正的价值不在于命令多高级,而在于它给了你一个统一的、从"文件描述符"视角审视系统的能力。你不需要装任何 agent,不需要依赖监控平台,只要 ssh 能登上去,lsof 就是那个帮你把系统内部打开的文件、连接、进程之间的关系全部摊开在面前的工具。
最后再分享一个小技巧:把 lsof -nP -i 和 watch 配合起来用,几秒钟刷新一次,观察一个进程的端口连接变化趋势,这在排查连接泄漏时比事后看快照有用得多。命令很简单,但我在很多次线上问题里都靠这一招找到了规律。
