
Linux 服务报出 Too many open files 时,磁盘通常没有问题。它表示某个进程或整个系统已经无法继续分配文件描述符。网站可能随之出现连接失败、接口超时、Nginx 502、数据库连接异常,日志中也可能看到 EMFILE 或 ENFILE。
文件描述符不只对应普通文件。网络套接字、管道、日志文件、设备和监听端口都会占用描述符。因此,排查时不能只盯着“打开了多少日志”,还要检查连接数量、程序是否泄漏,以及限制究竟来自哪一层。
一、先判断是哪个服务触发了限制
先查看故障时间附近的系统日志。下面以 Nginx 为例:
journalctl -u nginx --since "30 minutes ago" --no-pager
也可以在系统日志中搜索相关报错:
journalctl --since "30 minutes ago" --no-pager | grep -Ei "too many open files|EMFILE|ENFILE"
常见含义如下:
EMFILE:当前进程达到自己的文件描述符上限。ENFILE:系统级打开文件表接近或达到上限。
大多数线上故障属于前一种。确认服务名后,先取得主进程 PID:
systemctl show nginx -p MainPID --value
如果输出为 0,说明服务当前未运行,或者该单元没有可用的主进程。此时可结合 systemctl status nginx 和应用自身日志继续确认。
二、查看进程已经使用多少文件描述符
假设进程 PID 为 1234,可以统计 /proc 中的描述符数量:
find /proc/1234/fd -maxdepth 1 -type l 2>/dev/null | wc -l
再查看这个进程的软限制和硬限制:
grep "Max open files" /proc/1234/limits
输出通常包含 Soft Limit 和 Hard Limit。进程实际运行时不能超过软限制;软限制又不能高于硬限制。
还可以用 lsof 查看描述符主要被什么占用:
lsof -nP -p 1234
只想看数量时,可使用:
lsof -nP -p 1234 2>/dev/null | wc -l
lsof 的输出包含表头,而且进程状态可能在统计期间变化,因此数量可能与 /proc/1234/fd 略有差异。排障时看趋势和占用类型即可,不必要求两个数字完全一致。
三、找出占用文件描述符最多的进程
不知道具体是哪个服务时,可以按进程统计 /proc/*/fd。下面的命令只读取当前用户有权限访问的进程:
for p in /proc/[0-9]*; do
n=$(find "$p/fd" -maxdepth 1 -type l 2>/dev/null | wc -l)
name=$(cat "$p/comm" 2>/dev/null)
printf "%8s %-8s %s\n" "$n" "${p##*/}" "$name"
done | sort -nr | head -20
第一列是描述符数量,第二列是 PID,第三列是进程名。执行期间进程可能退出,少量读取失败属于正常情况。
如果某个进程的描述符数量持续上涨,长时间不回落,需要继续判断它是在承载正常高并发,还是存在连接、文件或管道未释放的问题。
四、检查描述符都用在了哪里
查看某个进程中最常见的描述符类型:
lsof -nP -p 1234 | awk 'NR>1 {print $5}' | sort | uniq -c | sort -nr
常见类型包括:
REG:普通文件,可能是日志、缓存或业务数据文件。IPv4、IPv6:网络连接和监听套接字。FIFO:命名管道或进程间通信。CHR:字符设备。
如果网络套接字占比很高,可进一步查看连接状态:
ss -s
ss -antp | head -100
大量 CLOSE-WAIT 往往说明应用没有及时关闭对端已经断开的连接;大量长时间存在的空闲连接,则要检查连接池、Keep-Alive、上游超时和客户端行为。
如果普通文件占比异常,可以列出打开次数较多的路径:
lsof -nP -p 1234 | awk 'NR>1 && $5=="REG" {print $9}' | sort | uniq -c | sort -nr | head -30
文件名包含空格时,这个统计命令只适合快速观察。需要精确分析时,应直接查看完整的 lsof 输出或使用程序自身的诊断工具。
五、systemd服务应该怎样调整限制
现在多数 Linux 服务器通过 systemd 管理 Nginx、Docker、Redis、Java 和自建服务。先查看服务当前的限制:
systemctl show nginx -p LimitNOFILE
如果确认业务并发量确实需要更高限制,可创建覆盖配置:
sudo systemctl edit nginx
写入:
[Service]
LimitNOFILE=65535
保存后重新加载 systemd 配置,并在合适的维护时间重启服务:
sudo systemctl daemon-reload
sudo systemctl restart nginx
重启会中断现有连接。生产环境应先检查配置、确认负载均衡或备用节点可用,再安排滚动重启。
重启后重新取得 PID,并验证实际限制:
pid=$(systemctl show nginx -p MainPID --value)
grep "Max open files" "/proc/$pid/limits"
不要直接修改软件包自带的 unit 文件。升级软件时,这类改动可能被覆盖;systemctl edit 生成的 drop-in 配置更容易维护和回滚。
六、为什么修改ulimit或limits.conf没有生效
在终端执行:
ulimit -Sn
ulimit -Hn
看到的是当前 Shell 的软限制和硬限制。使用 ulimit -n 65535 修改后,只会影响当前 Shell 以及从它启动的子进程,不会自动改变已经运行的服务。
/etc/security/limits.conf 和 /etc/security/limits.d/*.conf 主要通过 PAM 影响登录会话。例如:
www-data soft nofile 65535
www-data hard nofile 65535
这类配置通常不直接控制 systemd 系统服务。由 systemd 启动的服务应优先使用 LimitNOFILE=,并以 /proc/PID/limits 的实际结果为准。
七、检查系统级文件表是否接近上限
查看系统级限制:
sysctl fs.file-max
cat /proc/sys/fs/file-nr
fs.file-max 是系统可分配文件句柄的上限。file-nr 用于观察系统当前文件句柄的分配情况;不同内核版本的统计细节可能不同,判断时应结合当前内核文档和持续监控数据。
如果日志明确出现系统级文件表耗尽,并且确认不是单个应用泄漏,可以临时调整:
sudo sysctl -w fs.file-max=2097152
需要长期生效时,在 /etc/sysctl.d/ 中建立单独配置,例如:
echo 'fs.file-max = 2097152' | sudo tee /etc/sysctl.d/90-file-max.conf
sudo sysctl --system
数值应根据内存、业务并发和监控结果确定。提高系统上限不会修复程序泄漏,设置过大也不能替代容量规划。
八、临时恢复服务时应该怎么做
线上服务已经无法接受新连接时,可以按下面的顺序处理:
- 保存故障日志、进程 PID、
/proc/PID/limits、描述符数量和lsof结果。 - 确认是否存在异常连接、日志句柄、临时文件或明显泄漏。
- 有备用节点时先切走流量,再重启故障实例释放描述符。
- 根据正常峰值调整服务限制,同时修复连接或文件未关闭的问题。
- 增加描述符使用量监控,观察修复后是否再次持续增长。
不要在没有记录现场信息的情况下反复重启。重启会清空进程级证据,后续很难判断是限制偏低,还是应用持续泄漏。
九、建议增加哪些监控
至少记录以下指标:
- 关键进程已打开文件描述符数量。
- 进程
Max open files软限制。 - 描述符使用率,而不只是绝对数量。
- 系统级文件句柄分配情况。
- TCP 连接总数及
ESTABLISHED、TIME-WAIT、CLOSE-WAIT状态。 - 应用连接池、请求量和错误率。
告警阈值应留出处理时间。例如进程描述符使用率持续超过 70% 时预警,超过 85% 时提高告警级别。具体阈值还要结合业务峰值和增长速度调整。
总结
Too many open files 的排查重点是找到限制层级和描述符去向。先从报错服务定位 PID,再检查 /proc/PID/fd、/proc/PID/limits 和 lsof。systemd 服务使用 LimitNOFILE= 调整进程限制;登录会话才主要受 ulimit 和 PAM limits 配置影响。只有确认系统级文件表不足时,才考虑修改 fs.file-max。
限制调高后仍需观察描述符数量是否持续增长。正常高并发需要合理容量,程序泄漏则需要修复连接、文件或管道的释放逻辑。




