一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Linux如何排查系统句柄数超限问题:修改file-max与ulimit

时间:2026-06-04 08:04:07 编辑:袖梨 来源:一聚教程网

应先分层验证系统级(file-nr第二列)与进程级(/proc/pid/limits)实际使用量,再定位泄漏源而非盲目调参;systemd服务需配LimitNOFILE,容器须在运行时指定ulimit,且file-max须≤nr_open以防静默失败。

句柄超限不是“调大就完事”,而是先确认是不是真满了、谁在吃、为什么不吃完不吐——否则改完 file-maxulimit 只是把崩溃时间往后拖一天。

怎么看系统到底满没满

别只看报错,得拆成两层验证:系统级总量 vs 进程级限额。

  • cat /proc/sys/fs/file-max 是内核允许的全局上限,比如 1048576;但真正压垮系统的,往往是第二列:cat /proc/sys/fs/file-nr 输出三列,第二列才是“已分配未释放”的 fd 数,它接近第一列才危险
  • ulimit -n 显示当前 shell 的软限制,默认常为 1024;但进程可能继承了更小的值,得查真实运行时:用 cat /proc/$(pidof nginx)/limits | grep "Max open files",看 Soft Limit 到底是多少
  • 如果 lsof -p <pid> | wc -l</pid> 结果远超 Soft Limit,那进程早该报 Too many open files ——但没报?说明它根本没走标准 open() 路径(比如用了 memfd_create()),或者被 systemd 的 LimitNOFILE 覆盖了

为什么改了 /etc/security/limits.conf 却不生效

因为大多数现代 Linux 发行版(尤其是用 systemd 的)根本不会读这个文件来启动服务。

  • 对交互式登录用户:改 /etc/security/limits.conf 后必须新开一个 login shell(不是 sussh 子会话),且要确认 /etc/pam.d/common-session 包含 session required pam_limits.so
  • 对 systemd 服务(如 nginx、redis):limits.conf 完全无效,必须在 service 文件里加 LimitNOFILE=65536,例如写到 /etc/systemd/system/nginx.service.d/override.conf 中再 systemctl daemon-reload
  • 容器环境(Docker/K8s):宿主机改了没用,得在 docker run --ulimit nofile=65536:65536 或 Pod 的 securityContext.fdsLimit 里设

file-maxnr_open 不能乱调

file-max 不是越大越好,它直接消耗内核内存(每个 fd 约占 1KB),而 nr_open 是单个进程能申请 fd 的硬上限,必须 ≥ file-max,否则连 ulimit -n 都设不到想要的值。

  • 临时调大:sysctl -w fs.file-max=2097152,但重启失效
  • 永久生效:写入 /etc/sysctl.conf,同时检查 fs.nr_open 是否够用,可用 cat /proc/sys/fs/nr_open 查;若不够,需在内核启动参数加 nr_open=2097152(修改 /etc/default/grubupdate-grub && reboot
  • 注意:某些云厂商镜像(如阿里云 CentOS)默认 nr_open 只有 1048576,你设 file-max=2097152 会静默失败,sysctl -p 也不报错

真正该花时间的地方:定位泄漏而非调参

调参顶多买 2 小时窗口,泄漏不修,下次还爆。重点看三类 fd:

  • 大量 socket 处于 CLOSE_WAIT:说明本端没 close(),常见于 Java 的 Socket 忘关、Node.js 的 net.Socketdestroy()
  • 一堆 anon_inodeeventpoll:epoll 实例没 close(),多见于 C/C++ 自研网络库或 Go 的 net.Conn 泄漏
  • 重复路径的日志文件(如 /var/log/app.log.1, .2, .3):logrotate 没配 copytruncate 或程序没响应 SIGHUP,导致旧 fd 一直挂着
  • lsof -p <pid> | awk '$5 ~ /IPv|sock/ {print $9}' | sort | uniq -c | sort -nr</pid> 快速筛出高频 socket 目标,比干看数字有用得多

最常被忽略的一点:很多“泄漏”其实来自子进程继承了父进程的 fd,但没设 FD_CLOEXEC 标志——尤其 fork+exec 场景下,父进程开了 5000 个连接,子进程一启就平白多 5000 个 fd,却没人意识到。

热门栏目