Linux 文件句柄耗尽排查工作流:从 ulimit 到服务限制放大
来源:17golang原创
时间:2026-06-17 18:22:25 482浏览 收藏
线上服务突然出现 too many open files,表面看是 Linux 限制太小,实际可能是连接没有释放、日志句柄堆积、进程限制没生效,或者 systemd 服务没有重新加载配置。本文用一套完整工作流,把症状确认、进程定位、限制核对、配置放大和回归验证串起来。
适合正在维护 Linux 服务、Nginx、Java、Go、PHP-FPM、数据库客户端或后台任务的同学。命令以常见 systemd 服务器为例,其他发行版也可以按同样思路替换工具。
- 目标和边界:先确认要解决哪一类句柄问题
- 全流程总览:从报错到长期修复
- 阶段 1:确认症状和影响范围
- 阶段 2:找到句柄占用最高的进程
- 阶段 3:核对用户限制和服务限制
- 阶段 4:放大服务单元限制并重启
- 推荐工作流:一次排查怎么走
- 容易踩坑:为什么改了 ulimit 仍然没生效
- 速查表:常用命令和检查点
目标和边界:先确认要解决哪一类句柄问题
文件句柄不只对应普通文件。TCP 连接、Unix Socket、日志文件、临时文件、动态库、管道都可能占用 fd。排查目标不是盲目把限制调大,而是先回答三个问题:
- 是单个服务触顶,还是整台机器资源紧张?
- 是限制过小,还是程序持续泄漏句柄?
- 修改的是交互 Shell 限制,还是服务单元真实限制?
本文关注应用服务常见的 EMFILE、too many open files、连接建立失败、日志写入失败等场景。内核参数深度调优、容器编排平台统一配额不展开,只保留运维排查中最容易用上的路线。
全流程总览:从报错到长期修复
先说结论:文件句柄问题要按“报错证据 -> 进程占用 -> 当前限制 -> 配置修改 -> 复查监控”的顺序处理。只改 /etc/security/limits.conf,很多时候不会影响已经由 systemd 拉起的服务。

| 阶段 | 目标 | 关键动作 | 检查点 |
|---|---|---|---|
| 确认症状 | 证明问题确实和 fd 有关 | 看日志、状态码、系统消息 | 出现 open files 或 EMFILE |
| 定位进程 | 找出谁占用最多 | 查看进程 fd 数量和连接状态 | 某个 PID 明显接近上限 |
| 核对限制 | 确认限制来源 | 检查 shell、进程、服务配置 | Max open files 与预期一致 |
| 修改配置 | 让服务获得更高上限 | 写入 service override 并重启 | 进程 limits 已更新 |
| 回归验证 | 确认不再触顶 | 压测、观察 fd 增长和错误日志 | 错误消失且 fd 曲线稳定 |
阶段 1:确认症状和影响范围
目标:先拿到证据,避免把网络抖动、磁盘满、权限问题误判成句柄耗尽。
关键动作:看应用日志、系统日志和服务状态。常见信号包括 too many open files、EMFILE、accept failed、socket failed、日志文件无法打开。
journalctl -u myapp --since "10 min ago" --no-pager tail -n 200 /var/log/myapp/app.log dmesg -T | tail -n 50
工具选择:journalctl 适合 systemd 服务,应用自己的日志适合看业务报错,dmesg 适合补充内核层面的异常。
检查点:如果日志中明确出现文件、socket 或 open files 相关报错,再进入下一阶段。否则先回到网络、权限、磁盘空间等方向继续确认。
阶段 2:找到句柄占用最高的进程
目标:找出是哪个 PID 接近上限。不要只看服务名,因为一个服务可能有 master、worker、sidecar、定时任务等多个进程。
关键动作:先用 pidof 或 pgrep 找进程,再统计 /proc/PID/fd 数量。
pgrep -af myapp ls /proc/1234/fd | wc -l lsof -p 1234 | wc -l ss -s
工具选择:/proc/PID/fd 速度快,适合先看数量;lsof 更直观,适合看打开了哪些文件和连接;ss -s 可以辅助判断 TCP 连接是否异常堆积。
检查点:如果 fd 数量持续增长,说明可能有泄漏或连接回收问题;如果 fd 数量稳定但接近上限,通常是服务限制配置偏小。
阶段 3:核对用户限制和服务限制
目标:确认进程实际拿到的限制是多少。很多人登录机器后运行 ulimit -n,发现值很大,就以为服务也生效了,这是一个常见误区。
关键动作:同时看当前 Shell、目标进程和 systemd 服务配置。
ulimit -n cat /proc/1234/limits | grep "Max open files" systemctl show myapp -p LimitNOFILE systemctl cat myapp
工具选择:/proc/PID/limits 是最可靠的结果,因为它展示的是进程真实限制;systemctl show 用来判断 systemd 是否给服务设置了独立上限。
检查点:如果 /proc/PID/limits 里的 Max open files 仍然是 1024、4096 或明显低于预期,就需要从服务配置层修复。
阶段 4:放大服务单元限制并重启
目标:让服务真实获得新的 fd 上限,而不是只修改登录用户环境。
关键动作:优先使用 systemd override,不直接改发行版自带的 service 文件。这样升级包或部署脚本覆盖服务文件时,不容易丢配置。

sudo systemctl edit myapp
[Service] LimitNOFILE=65535
sudo systemctl daemon-reload sudo systemctl restart myapp pid=$(pgrep -n myapp) cat /proc/$pid/limits | grep "Max open files"
工具选择:systemctl edit 会生成 override 文件,适合长期维护;daemon-reload 让 systemd 重新读取配置;服务重启后再看新 PID 的 limits。
检查点:新进程的 Max open files 应该显示为你配置的值,比如 65535。如果仍然没变,说明 override 没写到正确服务、服务没有重启,或还有上层容器/进程管理器限制。
推荐工作流:一次排查怎么走
可以按下面顺序处理,既能快速止血,也能避免把真正的句柄泄漏掩盖掉。
- 从日志确认是否有 open files、EMFILE、socket 打开失败等明确报错。
- 用
pgrep -af 服务名找到目标 PID。 - 用
ls /proc/PID/fd | wc -l看当前 fd 数量。 - 用
cat /proc/PID/limits看进程真实上限。 - 如果数量接近上限,先提高
LimitNOFILE并重启服务。 - 如果数量持续增长,继续用
lsof -p PID看是连接、日志、临时文件还是重复打开的资源。 - 做一次压测或高峰观察,确认错误消失,fd 曲线不再一路上升。
容易踩坑:为什么改了 ulimit 仍然没生效
坑 1:只改了登录 Shell
ulimit -n 只代表当前 Shell。systemd 服务通常不会继承你手动登录后的限制,所以最终要看 /proc/PID/limits。
坑 2:只 reload,没有 restart
daemon-reload 只是让 systemd 重新读配置,已经运行的进程不会自动变成新限制。需要重启服务,让新进程带着新限制启动。
坑 3:限制放大后忽略泄漏
如果 fd 数量从 1000 涨到 60000,只是让故障晚一点出现。修复上限后仍要观察 lsof 结果,确认连接、文件、管道是否有异常堆积。
坑 4:容器里还有一层限制
如果服务跑在容器内,宿主机 systemd 配置只是其中一层。还要检查容器启动参数、编排平台资源限制,以及容器内进程的 /proc/PID/limits。
速查表:常用命令和检查点
| 要看什么 | 命令 | 结果怎么看 |
|---|---|---|
| 服务日志 | journalctl -u myapp --since "10 min ago" |
是否出现 open files 或 socket 打开失败 |
| 目标进程 | pgrep -af myapp |
确认 PID,不要看错 worker |
| 当前 fd 数量 | ls /proc/PID/fd | wc -l |
是否接近上限,是否持续增长 |
| 进程真实限制 | cat /proc/PID/limits |
看 Max open files 是否符合预期 |
| 服务限制 | systemctl show myapp -p LimitNOFILE |
确认服务管理层是否配置 |
| 打开对象详情 | lsof -p PID | head |
判断是文件、连接还是管道堆积 |
总结一下:Linux 文件句柄耗尽不要只记一个 ulimit -n。更稳的做法是先抓日志证据,再看目标进程 fd 数量,接着以 /proc/PID/limits 为准确认真实上限,最后用 systemd override 放大限制并重启验证。这样既能快速恢复服务,也不会把长期资源泄漏藏起来。
-
248 收藏
-
243 收藏
-
426 收藏
-
360 收藏
-
387 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习