Watch at Ease看见世界,读懂生活。

科技 · 实用指南

ulimit 已提高仍提示 Too many open files?先证明 Linux 服务撞到了哪层上限

当前 shell 的 ulimit 不能说明已运行服务的真实限制。用失败进程的 PID 区分 RLIMIT_NOFILE、系统文件表和 inotify,再修改上限。

区分进程文件描述符、系统文件表和 inotify 上限的原创编辑图
原创概念图:同一句报错可能需要三条不同证据路径。 Watch at Ease

Linux 的“Too many open files”并不能直接证明是哪层上限失败。EMFILE 通常指调用进程的 RLIMIT_NOFILE,ENFILE 指系统级文件表;inotify 也可能因为用户已耗尽实例数而返回 EMFILE。只看一句报错无法下诊断。

当前 shell 里很高的 ulimit 也不能证明正在运行的服务拥有同样限制。先固定失败时间和 PID,读取进程实际继承的限制并测量描述符,再检查系统和 inotify 分支。

1. 固定失败进程,而不是查看自己的 shell

重启前记录报错时间和服务 PID。systemd 服务可用 systemctl show UNIT -p MainPID -p LimitNOFILE,再读取 /proc/PID/limits 并统计 /proc/PID/fd。管理员稍后执行的 ulimit -n 只描述该 shell 及其未来子进程。

分别记录软、硬限制,把描述符数量与软限制比较,并用 readlink 抽样查看目标。不要把 lsof -u USER | wc -l 当作单进程计数,它会混合多个进程并出现重复。

2. 区分 EMFILE、ENFILE 与 inotify

若应用暴露 errno,EMFILE 常表示进程越过 RLIMIT_NOFILE,ENFILE 表示系统文件表耗尽。系统分支要比较 file-nr 与 file-max,并查找内核日志里的 file-max reached 信号。不要只因通用报错就提高 file-max。

若失败操作正在创建监视或日志明确提到 watch,就检查 inotify。inotify_init 的 EMFILE 可能来自用户实例上限,也可能来自进程 FD 上限;还要分别查看 max_user_instances 和 max_user_watches。

3. 追踪服务从哪里继承限制

先识别进程管理器。PAM 登录限制、shell ulimit、容器设置、应用启动器与 systemd 单元是不同继承链。systemd 服务应先检查实际加载的单元和 drop-in;LimitNOFILE 可用一个值同时设置软硬限制,也可写成 soft:hard。

不要把一个巨大数字复制到所有服务。systemd 文档警告,使用 select 的程序无法处理 1023 以上的描述符。只在确认程序支持后提高软限制,并优先使用本地 drop-in,保留旧值便于回滚。

4. 区分正常并发与泄漏

一次计数无法证明需求是否正常。应在相似负载下反复采样同一 PID,按 socket、pipe、普通文件和匿名描述符分组,并与应用指标比较。工作结束后仍只增不减是泄漏线索,但不是单独证据。

重启可能暂时降低计数,却不会修复原因。保留重启前证据并寻找有界稳态。合法并发需要更多描述符时按实测容量留余量;某类描述符异常增长时先修复其拥有者。

5. 只改一个拥有者,再验证新子进程

只修改证据确认的层:进程限制改服务或启动器,泄漏改应用,系统文件表要经容量评审,inotify 则先确认具体消费者。需要新子进程时只重启一次,再读取新 PID、/proc/PID/limits 与描述符数量。

成功必须同时满足:目标进程继承了目标限制、真实负载下计数有余量、原报错不再出现。若没有 errno、无法确定描述符拥有者、拟设值超过 nr_open,或修改会掩盖无限泄漏,应停止并升级处理。

证明 Linux 服务文件描述符限制的原创检查表
原创概念图:验证继承上限、当前压力与其他竞争资源。 Watch at Ease