“too many open files”本质是文件描述符(fd)耗尽,需四步精准排查:先用cat /proc/sys/fs/file-nr、/proc/pid/limits和ls /proc/pid/fd/验证是否真满;再用lsof或/proc遍历定位高fd进程;接着按类型(ipv4/reg/anon_inode)分析泄漏根因;最后依启动方式(systemd/容器/jvm)正确配置limitnofile等参数并验证生效。

服务突然宕机,日志里反复出现 “Too many open files” 或 “accept failed: Too many open files”,基本可以锁定是文件句柄(file descriptor,FD)耗尽。这不是偶然峰值,而是泄漏或配置不当长期积累的结果。排查关键不是盲目调大数值,而是分四步定位真实瓶颈:确认是否真满、查清谁在吃、看懂吃的是什么、再决定怎么止血和修复。
先验证是不是真耗尽了
别被报错带偏节奏,得用数据说话:
- 查系统全局是否见顶:cat /proc/sys/fs/file-nr,重点看第二列(已分配未释放的 FD 数),如果接近第三列(file-max),说明整个系统资源紧张;
- 查出问题的服务进程本身上限:cat /proc/
/limits | grep "Max open files" ,Soft Limit 才是它实际撞墙的值,Hard Limit 是天花板; - 确认该进程当前用了多少:ls /proc/
/fd/ 2>/dev/null | wc -l ,这个数比 lsof 更准、更快; - 如果进程数持续上涨、且没回落,大概率是泄漏,不是临时高峰。
快速揪出“句柄大户”
一个服务挂了,未必是它自己开太多,也可能是其他进程把系统 FD 池子抽干了:
- 全局扫一遍谁最猛:lsof -n | awk '{print $2}' | sort | uniq -c | sort -nr | head -10;
- 更精准一点(避开 lsof 自身干扰):for pid in /proc/[0-9]*; do p=$(basename $pid); c=$(ls -1 $pid/fd 2>/dev/null | wc -l); [ $c -gt 500 ] && echo "$p $c"; done | sort -k2 -nr | head -10;
- 重点关注长期运行、频繁建连接(如 Nginx、Java 应用、Redis 客户端)、或大量 fork 子进程的服务。
看懂它打开的到底是什么
知道 PID 还不够,得知道它在开什么——不同类型的 FD 指向完全不同的根因:
- 统计类型分布:lsof -p
| awk '{print $5}' | sort | uniq -c | sort -nr ; - IPv4/IPv6 最多 → 网络连接堆积:检查连接池配置、下游超时重试逻辑、CLOSE_WAIT 是否异常堆积(ss -ant | awk '{print $NF}' | sort | uniq -c);
- REG 最多 → 文件没关:重点查日志轮转(logrotate 后进程是否 reload 或发 SIGUSR1)、配置文件热加载、mmap 后忘了 munmap(即使 close 了 fd,引用计数不为 0 也不会释放);
- anon_inode 大量存在 → epoll/eventfd/inotify 泄漏:常见于无节制注册 inotify watch,或异步框架未清理事件源。
配置改了到底有没有生效?
很多人改完 limits.conf 就重启服务,结果发现还是 1024 —— 因为启动方式不同,生效路径完全不同:
- systemd 服务(绝大多数线上服务):limits.conf 完全无效,必须在 service 文件中加 [Service] LimitNOFILE=65536,然后 systemctl daemon-reload && systemctl restart xxx;
- 容器环境(Docker/K8s):宿主机改没用,Docker 要加 --ulimit nofile=65536:65536,K8s 要在 Pod 的 securityContext.fdsLimit 显式设置;
- Java 应用:注意 JVM 启动脚本是否覆盖 ulimit(比如用 sudo 启动会重置限制),还要确认 -XX:+UseContainerSupport 在容器中是否影响 FD 计算;
- 改完一定要验证:cat /proc/
/limits 看 Soft Limit 是否更新,别信配置文件写了就等于生效。











