Linux 出现 Too Many Open Files 怎么办?文件描述符定位与 systemd 限额调整

2026-09-20

直接答案:Too many open files 通常表示某个进程达到 RLIMIT_NOFILE 软限制并收到 EMFILE,也可能是系统级文件句柄接近上限。先找到报错进程,读取 /proc/PID/limits,按文件、socket、pipe、匿名 inode 分类其打开描述符,并观察数量是否持续单调增长。若业务工作集合理,再为该 systemd 服务设置经过容量评估的 LimitNOFILE;若存在泄漏,必须修应用、连接池或文件关闭逻辑,不能只把限制从 1024 改成几十万。

本文适合 Web、数据库、代理或守护进程间歇报 EMFILE、accept 失败、连接异常的场景。Shell 中的 ulimit -n 只代表当前 shell,不能直接证明 systemd 启动的服务限额;容器还可能叠加运行时限制。

本文按 2026 年 9 月可查的官方文档核对。示例命令以只读取证为主;凡涉及删除、覆盖、重启、权限、网络或数据库参数的动作,都应先确认操作对象、备份、维护窗口和带外恢复入口。不同发行版与软件版本的路径可能不同,必须先查询实际配置再改。

先用证据判断故障在哪一层

现象或检查点 它能说明什么 直接证据 不要误判为
单进程 fd 接近软限制 进程级 RLIMIT_NOFILE 是直接边界 /proc/PID/limits 与 /proc/PID/fd 数量 系统 fs.file-max 已耗尽
fd 数持续上升不回落 可能有文件或连接泄漏 按类型和时间采样 正常并发峰值
大量 ESTABLISHED/等待 socket 连接池、客户端或下游未释放 ss、lsof与应用指标 普通文件泄漏
系统 file-nr 接近上限 全机句柄资源紧张 /proc/sys/fs/file-nr 与 file-max 某服务软限制
手工启动正常、systemd启动失败 服务管理器限额不同 systemctl show LimitNOFILE 二进制版本不同

这张表的用法不是“看到一个关键词就改一个参数”,而是把客户端、DNS或边缘、Web 代理、应用、数据层依次分开。至少保留故障时间、完整 URL、状态码、关键响应头、同一时刻的服务日志和一份正常样本。只有时间能够对齐,才能判断两个现象是否属于同一次故障。

六步排查与修复顺序

1. 锁定进程与错误时间

从应用、Nginx 或系统日志找到具体 PID、服务名和首次报错时间。记录并发、连接数、发布版本和重启时间;重启会把 fd 计数清零,因此排障前先保存 /proc/PID/fd 数量与类型。

2. 比较软硬限制与实际使用

读取 /proc/PID/limits 和 systemd 的 LimitNOFILE,再统计当前 fd。限制值必须与正在运行的 PID 对应;旧进程不会因为编辑 unit 文件自动获得新限制,需在维护窗口重启服务后复验。

3. 按描述符类型找增长源

查看 fd 符号链接,区分普通文件、TCP/Unix socket、pipe、eventfd、inotify 和已删除文件。按分钟采样总数和主要类型;持续单调增长比故障时的一次高值更像泄漏。

4. 追到应用和连接池

将 socket 端点与应用线程、连接池、数据库和外部服务对齐,检查响应体/文件是否关闭、keepalive 是否合理、失败路径是否释放资源。CLOSE_WAIT 多常提示本地应用未正确关闭已由对端结束的连接。

5. 容量评估后再调限额

估算峰值连接、每请求文件/管道、后台任务和安全余量,同时核对内存、端口和下游容量。通过 systemd drop-in 调整服务专属 LimitNOFILE,不要只改 /etc/security/limits.conf 期待所有守护进程生效。

6. 重启与高峰双重验收

执行 daemon-reload 后在维护窗口重启目标服务,读取新 PID 的 limits,并用真实高峰或等价负载观察 fd 曲线。验收标准应包括错误消失、fd 达到平台后回落、内存和连接正常,而不是只看上限变大。

可复制的只读取证命令

以下命令中的域名、服务名、路径和时间范围要替换为自己的真实值。先执行查询命令并保存原始输出;不要把“命令能运行”误当成“业务已恢复”。

text pid=$(systemctl show -p MainPID --value your-service); echo $pid cat /proc/$pid/limits | grep -i 'open files' find /proc/$pid/fd -maxdepth 1 -type l 2>/dev/null | wc -l ls -l /proc/$pid/fd 2>/dev/null | sed -n '1,80p' systemctl show your-service -p LimitNOFILE -p MainPID

  • pid=$(systemctl show -p MainPID --value your-service); echo $pid:获取 systemd 主进程 PID;把服务名替换为真实值
  • cat /proc/$pid/limits | grep -i 'open files':读取运行进程的软硬文件描述符限制
  • find /proc/$pid/fd -maxdepth 1 -type l 2>/dev/null | wc -l:统计当前打开描述符数量
  • ls -l /proc/$pid/fd 2>/dev/null | sed -n '1,80p':抽样描述符指向,输出可能含敏感路径需脱敏
  • systemctl show your-service -p LimitNOFILE -p MainPID:查看 systemd 对该服务的限制与主进程

如命令输出包含公网 IP、用户名、Cookie、令牌、数据库连接串或客户数据,分享工单前先脱敏。对比时保留时间戳、时区和命令本身,否则后来无法复现同一观察条件。

五个最容易扩大故障的误区

  • 只运行 ulimit -n:它代表当前 shell,不一定代表 systemd 服务或容器进程。
  • 把限制直接改成百万:每个 fd 都有内核和应用成本,还可能把压力推给数据库或网络。
  • 先重启再取证:计数和泄漏类型会被清空,只剩无法验证的猜测。
  • 只看 lsof 行数:线程、内存映射和输出格式会影响统计,应以 /proc/PID/fd 和具体类型交叉验证。
  • 调整 limits.conf 后不查新 PID:systemd 服务可能不读取 PAM limits,修改也要重启服务才作用于新进程。

变更、回滚与数据边界

修改前保存 unit、drop-in、当前进程 limits 与 fd 基线。若提高限额后内存、连接池、下游数据库或系统 file-nr 越过阈值,恢复旧 drop-in 并重启目标服务,不影响其他服务。若已确认泄漏,应先限流或滚动重启缓解,再发布修复;单实例重启要考虑会话、队列和未完成写入。

如果只能先恢复服务、还不能确认根因,应把措施标记为“临时缓解”,例如限流、摘除单节点、切回上一配置或临时绕过一层代理。恢复后仍要在受控环境复现原因,并验证同类故障不会在下一次重启、发布或高峰重新出现。

统一验收清单

  • [ ] 报错进程与服务名已确认
  • [ ] 软硬限制来自运行PID而非当前shell
  • [ ] fd 总量和主要类型有时间序列
  • [ ] 泄漏或合理峰值已有证据结论
  • [ ] systemd drop-in范围仅限目标服务
  • [ ] 新PID读取到预期限额
  • [ ] 高峰后fd能回落
  • [ ] 内存、端口与下游容量未被转移压垮

验收必须同时覆盖机器内部和真实外部路径。首页返回 200 不能证明上传、长连接、登录、数据库写入或移动端都正常;同样,控制台显示“运行中”也不能证明应用已能处理请求。至少选一个正常请求、一个边界请求和一个失败请求,记录预期与实际结果。

相关内容与隔离测试入口

建议先阅读服务器负载高但CPU不高、服务器CPU 100%、监控告警怎么做,把本问题放回完整请求链路。若生产环境不适合直接试验,可查看美国弹性云服务器的当前规格与库存,搭建与生产隔离的复现环境;测试机不能替代生产备份,价格、库存和可选配置以实时产品页为准。

常见问题

ulimit -n 显示很大,为什么服务仍报错?

当前终端和 systemd 服务可能有不同 RLIMIT。读取 /proc/服务PID/limits 才是该进程实际值。

file-max 和 nofile 有什么区别?

nofile 是进程级描述符上限,file-max 是系统级文件句柄相关上限。排障要分别看,不应混为一个数字。

大量 socket 一定是泄漏吗?

不一定。真实高并发和 keepalive 会产生大量 socket;关键是与并发、状态和时间趋势是否匹配,以及高峰后是否释放。

为什么重启后几天又复发?

泄漏会从零重新累积。重启只是缓解,需通过曲线和类型定位未释放资源。

提高 LimitNOFILE 会影响性能吗?

上限本身未必显著耗费资源,但允许更多连接会增加内存、调度和下游压力,必须做整体容量预算。

官方参考资料

最近更新