现象
日志出现 Too many open files 或 accept failed: EMFILE;运行数小时后必然复现,重启只能短暂缓解;进程还活着但新连接被拒绝;/proc/$pid/fd 数量持续上涨。
诊断
先看当前 fd 数与进程限制:
pid=$(pgrep -f 'node server.js' | head -1)
ls /proc/$pid/fd | wc -l
grep 'Max open files' /proc/$pid/limits
ls -l /proc/$pid/fd | awk '{print $11}' | sort | uniq -c | sort -nr | head
数量贴近 soft limit 就是直接原因。链接目标里大量 socket 说明连接泄漏,重复出现的普通文件说明每次请求都打开了句柄。每隔几秒采样一次可以区分泄漏与容量压力:
for i in $(seq 1 5); do ls /proc/$pid/fd | wc -l; sleep 10; done
只涨不回落是泄漏,来回波动则说明并发超出预算。再用 cat /proc/sys/fs/file-nr; sysctl fs.file-max 判断是否系统级耗尽(ENFILE)。
根因
每个 socket、管道和打开的文件都占用一个描述符。进程要么 soft limit 低于真实并发,要么在异常路径上泄漏句柄;表满后内核在应用代码之前就拒绝新的 open 或 accept。
修复
在所有路径(包括异常和提前返回)关闭资源,用连接池硬上限把并发控制在描述符预算内;确认无泄漏后再提高限制:
# /etc/systemd/system/app.service.d/limits.conf
[Service]
LimitNOFILE=65535
sudo systemctl daemon-reload
sudo systemctl restart app.service
systemctl show app.service -p LimitNOFILE
ulimit -n 只影响当前 shell,不会改变 systemd 单元的限制;只调 /etc/security/limits.conf 对已运行的服务也不生效。
验证
重放触发错误的请求组合,每轮后采样 fd 数,应回落到基线;峰值低于 soft limit 且有余量;日志不再出现 EMFILE 或 ENFILE。