Linux 文件句柄耗尽排查:从 lsof 到 systemd LimitNOFILE

举报
yd_282288291 发表于 2026/11/09 09:08:22 2026/11/09
【摘要】 从进程级和系统级两层限制讲起,介绍用 lsof 与 /proc 定位 fd 增长源,整理 ulimit、systemd LimitNOFILE 与 fs.file-max 的正确配置,并总结连接池、子进程管道、日志轮转等常见泄漏点。

一、先分清是进程上限还是系统上限

服务突然报 Too many open files、accept failed,或者日志写不进去,通常不是磁盘问题,而是文件描述符(fd)耗尽。排查的第一步不是急着调大参数,而是分清两层限制:

  • 单进程限制:ulimit -n、/proc/<pid>/limits 里的 Max open files;
  • 系统总限制:fs.file-max,以及当前已分配数量 fs.file-nr。

如果只是某个进程到顶,其他进程正常,优先查这个进程有没有泄漏;如果系统级 file-nr 接近 file-max,才考虑整体扩容。把两者混为一谈,很容易出现“调了 ulimit 却没效果”的情况。

二、定位是谁占着 fd

最直接的办法是看进程自己的 fd 表:

ls /proc/<pid>/fd | wc -l
lsof -p <pid> | wc -l

两者数量接近时,说明统计可信。接着按类型归类,找出增长最快的那一类:

lsof -p <pid> -Fn | grep '^n' | sed 's/^n//' | \
  awk -F/ '{print $1"/"$2}' | sort | uniq -c | sort -rn | head

常见的高占用来源有:

  1. socket 未关闭:HTTP 客户端、数据库连接池、gRPC 流式调用忘记 close;
  2. 日志轮转未 reopen:logrotate 用 copytruncate 时旧 fd 仍指向被截断的文件;
  3. 子进程管道泄漏:subprocess.Popen 的 stdout/stderr 管道没有读取或关闭;
  4. inotify watch:文件监控工具对目录递归监听,watch 数量随文件增长;
  5. epoll/eventfd:高并发网络框架每个连接持有多个 fd,连接回收不及时就会堆积。

三、临时与永久提高上限

临时验证可以在当前 shell 里执行:

ulimit -n 65535

但这只影响当前 shell 及其子进程,重启或换个终端就失效。要让服务稳定生效,需要写到 systemd unit:

[Service]
LimitNOFILE=65535

改完后 systemctl daemon-reload && systemctl restart <service>,再用 /proc/<pid>/limits 确认实际值。注意 LimitNOFILE 同时设置 soft 和 hard,若只改 /etc/security/limits.conf,对 systemd 托管的服务往往不生效。

系统级可以检查:

sysctl fs.file-nr
sysctl fs.file-max

fs.file-nr 的三个数字分别是“已分配、未使用但已分配、上限”。只有在已分配持续贴近上限时,才需要调整 fs.file-max。

四、代码层的几个高频坑

Python 忘记关闭文件:

# 不推荐
f = open(path)
data = f.read()

# 推荐
with open(path) as f:
    data = f.read()

HTTP 连接池没有上限:使用 requests.Session 或自定义连接池时,要设置 pool_maxsize,并在异常路径确保连接释放。流式响应必须显式 close(),否则连接会一直占着 fd。

子进程管道:Popen 带 stdout=PIPE 却不读取,管道缓冲区满后子进程会阻塞,fd 也不会释放。可以用 communicate(),或者把输出重定向到文件。

日志轮转:如果使用 copytruncate,进程仍持有旧 fd,写入位置会错乱;更稳妥的是让进程支持 SIGHUP 重新打开日志文件,或在轮转后重启 worker。

五、监控与告警

不要等报错才查。至少采集三个指标:

  • 进程级 process_open_fds;
  • 系统级 filefd.allocated;
  • 每秒新建连接数与关闭数是否平衡。

给 fd 使用率设置阈值(例如 80% 预警、90% 告警),并保留 lsof 快照。这样出现泄漏时能直接对比“哪个文件/连接在增长”,而不是只看一个总数。

六、小结

文件句柄耗尽的排查顺序应该是:先确认是进程级还是系统级,再按 fd 类型找增长源,最后才调整 ulimit/LimitNOFILE。参数调大只能争取时间,真正解决问题的是关闭未释放的连接、修正日志轮转方式,以及给连接池和子进程加上明确的回收路径。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。