fd耗尽是linux“假死”的隐蔽元凶,因内核在tcp三次握手阶段拒连且不记日志,导致sshd失联、新连接失败但系统仍可ping通;需通过/proc/sys/fs/file-nr等命令快速定位并分层防控。

文件描述符(FD)耗尽是Linux系统“假死”的隐蔽元凶之一——它不触发OOM Killer,不写错误日志,甚至让sshd进程完全失联,而系统本身仍能ping通。问题根源在于:内核在TCP三次握手阶段就拒绝新连接,连SYN包都不进应用层,所以你查不到进程异常、看不到日志报错、用ps或top也一切正常。
为什么FD耗尽会导致“假死”而非崩溃
Linux把每个socket、pipe、open()的文件都算作一个FD。当全局FD池(/proc/sys/fs/file-nr中第一个数)逼近file-max上限时:
- 新accept()调用直接返回EMFILE错误,Web服务无法建立新连接
- sshd子进程fork失败,导致新SSH会话无法创建(但已有连接可能维持)
- 后台任务如日志轮转、健康检查脚本因open()失败而静默退出,加剧不可见性
- 内核不再记录此类拒绝,
dmesg和/var/log/messages通常空空如也
如何快速确认是FD耗尽引发的假死
即使SSH连不上,只要还能console直连或通过带外管理(如iDRAC/IPMI)访问,立即执行:
-
cat /proc/sys/fs/file-nr—— 若第一列接近第三列(如9223372036854775800 0 9223372036854775807),基本锁定 -
lsof -n | awk '{print $2}' | sort | uniq -c | sort -nr | head -5—— 查出FD大户PID -
ls -l /proc/[PID]/fd | wc -l—— 精确查看某进程打开了多少FD -
cat /proc/[PID]/limits | grep "Max open files"—— 看该进程是否被ulimit限制住,还是它自己泄漏了
两类典型泄漏场景与修复要点
场景一:长连接服务未关闭socket
如Node.js/Python服务中HTTP客户端复用连接池,但未设置超时或未正确close响应体,导致TIME_WAIT socket堆积且FD未释放。
- 临时缓解:kill掉泄漏进程(别只kill -HUP,要确保彻底退出)
- 代码修复:显式调用
response.close()或使用with上下文;设置keep_alive_timeout和max_connections
场景二:子进程继承父进程FD
主进程打开大量文件或监听端口后fork子进程,子进程未调用close()就exec新程序,导致FD被意外持有。
- 预防:fork前用
fcntl(fd, F_SETFD, FD_CLOEXEC)设关闭标志 - 排查:对比
lsof -p [parent_pid]和lsof -p [child_pid],看是否有不该出现的FD重复
永久防御机制不能只靠调大上限
把fs.file-max从默认的几十万调到百万,只是延缓爆炸时间。真正有效的防线是分层控制:
- 服务级:systemd服务配置
LimitNOFILE=65536,避免单个服务吃光全局FD - 用户级:在
/etc/security/limits.conf中为运行用户设软硬限制,如* soft nofile 65536 - 监控项:将
cat /proc/sys/fs/file-nr | awk '{print $1/$3*100}'纳入Prometheus+Alertmanager告警(建议阈值85%) - 启动检查:关键服务启动脚本开头加
[[ $(ulimit -n) -lt 65536 ]] && echo "FD limit too low" >&2 && exit 1











