最可靠的方法是读取 /proc/sys/fs/file-nr 的第二列,它表示当前系统活跃占用的文件句柄数;需结合 fs.file-max 和各进程 LimitNOFILE 统一调优,否则参数再大也仅延缓崩溃。

Linux 文件句柄(file descriptor)耗尽不是“会不会发生”的问题,而是“什么时候发生”的问题——尤其在短连接 HTTP 服务、日志密集型应用或 UDP 高频收发场景下,Too many open files 报错往往来得突然,但根源早已埋在 /proc/sys/fs/file-nr 的第二列里。
怎么准确查当前系统已用句柄总数
别信 lsof | wc -l:它会因权限失败中断、含表头和空行,计数漂移大;也别只看某个进程的 /proc/[pid]/fd/,局部高不代表全局瓶颈。
最可靠的是直接读内核统计:
-
cat /proc/sys/fs/file-nr输出三列,顺序是:已分配句柄数、已分配但未使用数、系统上限(fs.file-max)。真正该盯的是第二列 —— 它才是当前「活跃占用」的真实值 - 例如输出
12480 3210 2097152,说明已有 3210 个句柄正在被使用(第一列减第二列 = 已分配未用),离上限还很远;但如果第二列接近第三列的 80% 以上,就得预警 - 这个值是全系统聚合统计,不依赖用户权限,也不受
lsof进程自身开销干扰
为什么 ulimit -n 改了,程序还是报 Too many open files
因为 ulimit 只管「单进程软硬限制」,而 systemd 服务默认绕过 /etc/security/limits.conf —— 即便你配了 * soft nofile 65535,对 nginx、redis、java 应用等通过 systemd 启动的服务完全无效。
必须在 service 文件里显式声明:
- 编辑对应 unit 文件,比如
/etc/systemd/system/nginx.service - 在
[Service]段下添加:LimitNOFILE=65535(注意不是nofile,是LimitNOFILE) - 执行
sudo systemctl daemon-reload && sudo systemctl restart nginx生效 - 验证方式:
cat /proc/$(pgrep nginx | head -n1)/limits | grep "Max open files",确认Soft Limit和Hard Limit已更新
系统级 file-max 调多少才够用
别按“经验值”硬设 65536 或 1048576。真实需求取决于并发连接数 ×(每个连接平均打开的文件数 + 日志/配置/临时文件等额外开销)。
粗略估算公式:file-max ≥ 并发连接峰值 × 3 ~ 5。例如某 API 服务峰值 2 万 TCP 连接,建议 fs.file-max ≥ 100000。
调整时注意两点:
- 临时改用:
sudo sysctl -w fs.file-max=100000,立即生效但重启丢失 - 永久生效必须写入
/etc/sysctl.conf,且要确保sysctl -p执行成功(检查有无报错,部分发行版需加-f强制) - 改完别忘了同步调高各关键服务的
LimitNOFILE,否则系统有容量,进程却卡在自己的限制里
哪些地方容易漏掉句柄泄漏线索
句柄泄漏不总表现为持续增长,也可能在流量高峰后残留不释放 —— 此时 file-nr 第二列缓慢爬升,但 lsof -p [pid] | wc -l 看不出明显异常。
更有效的排查路径:
- 用
lsof -n -p [pid] | awk '$5 ~ /^[0-9]+[uw]/ {print $5}' | sort | uniq -c | sort -nr查看该进程 FD 类型分布,如果大量0u(stdin)、1u(stdout)或重复的 socket 地址,大概率是子进程继承了父进程句柄但没 close - 检查是否用了
fork()后未close()父进程打开的 socket 或日志文件描述符 - Java 应用注意
FileInputStream是否都套了 try-with-resources;Go 注意os.Open后是否 deferClose();Node.js 留意fs.createReadStream是否监听了close事件
真正棘手的从来不是调参,而是发现某个进程明明只开了几百个连接,/proc/[pid]/fd/ 下却躺着上万个符号链接 —— 那说明代码里早有句柄没关,参数再大也只是延缓崩溃时间。











