当前位置:首页 > 文章列表 > 文章 > linux > 进程句柄数持续上涨:用 procfs 找到未关闭资源

进程句柄数持续上涨:用 procfs 找到未关闭资源

来源:17golang原创 2026-10-07 14:29:07 0浏览 收藏

Linux 进程的句柄数持续上涨,最直接的排查入口不是先安装工具,也不是立刻调高 nofile,而是查看 /proc//fd。这个目录为进程当前维护的文件描述符提供符号链接;再结合 fdinfo、limits 和系统级 file-nr,通常可以把增长归到未关闭的文件、网络连接、管道或事件监听器。

官方参考:https://www.kernel.org/doc/html/latest/filesystems/proc.html

排查目标是找到“哪一类描述符稳定累积、链接到什么资源、哪个模块应该关闭它”。单次句柄总数只能说明现象,按目标聚类后的持续增长才接近根因。

先区分进程上限和系统上限

文件描述符是进程访问文件、socket、pipe、eventfd、epoll 等内核对象时使用的整数索引。遇到 Too many open files 时,需要区分三个不同概念:

位置含义是否等于当前打开数
/proc/PID/fd目标进程当前可见的文件描述符链接接近当前值,目录读取期间仍可能变化
/proc/PID/limits 的 Max open files目标进程 RLIMIT_NOFILE 的软、硬限制否,这是上限
/proc/sys/fs/file-nr系统级已分配文件句柄等统计不是某一个进程的数量
/proc/PID/status 的 FDSize当前分配的描述符槽位容量否,不能拿来当打开数

先读取限制和系统水位,但不要修改它们:

pid=12345

# 查看目标进程的软限制与硬限制。
grep -E '^Max open files' "/proc/$pid/limits"

# 查看系统级已分配、空闲和最大文件句柄统计。
cat /proc/sys/fs/file-nr
cat /proc/sys/fs/file-max

如果单进程打开数持续增长,即使距离上限还很远,也应继续查泄漏。提高限制只能延后失败时间;只有确认业务确实需要更高并发句柄且生命周期正常时,调整上限才是容量规划。

进程文件描述符表与 procfs 的 fd、fdinfo、limits 和系统文件句柄统计关系
图1:进程描述符表、procfs 观测入口与系统限制的静态结构说明图,不是运行截图。

连续采样 /proc/PID/fd

单次计数无法区分正常波动和持续泄漏。应在负载形态相近的窗口连续采样,例如同一批请求、同一消费速率或同一后台任务周期。下面的脚本每隔 10 秒读取一次目录项数量:

#!/usr/bin/env bash
set -u

pid="${1:?请传入目标 PID}"
interval="${2:-10}"

# 进程退出或权限不足时停止,避免把空目录误记为 0。
while [[ -d "/proc/$pid/fd" ]]; do
  if count=$(find "/proc/$pid/fd" -mindepth 1 -maxdepth 1 -printf '.' 2>/dev/null | wc -c); then
    printf '%(%F %T)T\tpid=%s\tfd=%s\n' -1 "$pid" "$count"
  else
    printf '无法读取 /proc/%s/fd,请检查 PID 与权限\n' "$pid" >&2
    exit 1
  fi
  sleep "$interval"
done

procfs 是活动进程的即时视图,打开和关闭可能恰好发生在枚举期间,因此相邻样本出现少量抖动并不异常。更有意义的信号是:相同负载下基线不断抬高,空闲后也不回落,且某一类目标数量同步增加。

长时间监控还要防止 PID 被复用。最简单的做法是同时核对 /proc/PID/exe 和服务管理器记录的主进程身份;若原进程已经退出,就结束这轮采样,不要把新进程拼到旧曲线上。

按符号链接目标聚类

/proc/PID/fd/N 是符号链接。普通文件通常指向路径,已删除但仍被进程持有的文件会带 (deleted),网络连接显示为 socket:[inode],管道显示为 pipe:[inode],epoll、eventfd、inotify 等常以 anon_inode: 开头。

pid=12345
snapshot="fd-targets-${pid}.tsv"

# 保存“描述符编号 + 链接目标”,读取失败的瞬时项直接跳过。
: > "$snapshot"
for fd in "/proc/$pid/fd/"*; do
  [[ -e "$fd" || -L "$fd" ]] || continue
  target=$(readlink "$fd" 2>/dev/null) || continue
  printf '%s\t%s\n' "${fd##*/}" "$target" >> "$snapshot"
done

# 按资源大类汇总,先看哪一类随时间增长。
awk -F '\t' '
  $2 ~ / \(deleted\)$/ {count["deleted"]++; next}
  $2 ~ /^socket:\[/     {count["socket"]++; next}
  $2 ~ /^pipe:\[/       {count["pipe"]++; next}
  $2 ~ /^anon_inode:/    {count["anon_inode"]++; next}
  {count["file_or_other"]++}
  END {for (kind in count) print kind, count[kind]}
' "$snapshot" | sort

接着对连续两到三个快照做同样分类。如果只有 socket 数增长,就把注意力放在连接池、HTTP 响应体、失败重试与超时取消;如果 (deleted) 文件增长,优先检查日志轮转后旧文件是否仍被持有;如果 pipe 增长,检查子进程标准流和管道端点;如果 anon_inode 增长,则查看 epoll、eventfd、timerfd、inotify 等对象的创建和销毁。

用 fdinfo 缩小具体资源

链接目标只给出大类。/proc/PID/fdinfo/FD 至少可提供打开偏移 pos、八进制打开标志 flags、挂载 ID mnt_id 与 inode ino;某些对象还会暴露该类型专有字段。可以先挑选增长簇中的几个 FD 查看:

pid=12345
fd=57

# 先确认链接目标,再读取该描述符的内核元数据。
readlink "/proc/$pid/fd/$fd"
cat "/proc/$pid/fdinfo/$fd"

排查时可把“目标路径 + inode + flags”作为同一资源簇的特征。大量描述符指向同一路径但 inode 相同,通常说明重复打开没有关闭;大量不同 socket inode 则更像连接生命周期问题。由于 FD 编号可被进程重复利用,不要只盯着数字 57 本身,应比较它指向的目标和元数据。

把资源簇映射回关闭责任

procfs 能告诉你“进程持有什么”,不能直接指出哪一行代码忘了关闭。下一步是把资源类型映射到明确的生命周期负责人:

增长特征常见责任边界优先检查
普通文件路径重复文件读取、导出、上传、日志模块成功、失败和提前返回路径是否都执行 close
路径带 (deleted)日志轮转、临时文件、部署替换旧文件是否仍被长寿命进程持有
socket:[inode] 持续增加HTTP 客户端、数据库、消息队列、连接池响应体、连接、超时与重试是否完整收尾
pipe:[inode] 持续增加子进程、流式处理、进程间通信父子两端是否在所有退出路径关闭
anon_inode 持续增加事件循环、监听器、定时器watcher、epoll、eventfd 的注销与关闭

如果服务由多个工作线程共享同一资源管理器,关闭责任应归到拥有生命周期的组件,而不是在任意调用点补一个 close。否则虽然句柄数暂时下降,却可能引入并发使用已关闭资源的问题。

普通文件、删除文件、socket、pipe 和 anon_inode 与资源拥有者及关闭责任的关系
图2:不同 procfs 链接特征与资源拥有者、关闭责任的静态映射说明图,不是运行截图。

修复后做同负载复查

修复后用相同负载、相同采样间隔和相同持续时间重跑计数。理想结果不是句柄数绝对不变,而是达到稳定区间:请求或任务开始时上升,完成后回落,长期基线不再单调增长。还应分别覆盖成功、超时、取消、解析失败、远端断开和服务停止等路径。

  • 数量:/proc/PID/fd 的基线是否停止抬高;
  • 类型:原先增长的资源大类是否不再累积;
  • 目标:相同路径、socket 或 anon_inode 簇是否回落;
  • 限制:稳定峰值是否与 Max open files 保留足够余量;
  • 权限:监控账号是否只获得读取所需 procfs 信息的最小权限。

常见问题

为什么 lsof 数量和 /proc/PID/fd 不完全一致?

两者读取活动进程时都可能遇到并发打开和关闭,工具的分类、权限和统计口径也可能不同。排查趋势时固定一种方法更重要;本文以 procfs 目录项和链接目标为统一口径。

看到很多 socket 就能认定泄漏吗?

不能。高并发连接池本来就可能长期保持大量 socket。需要结合相同负载下的基线、空闲后的回落、socket 状态和应用生命周期判断,持续增加且不复用才更可疑。

删除日志文件后磁盘空间为什么没有释放?

如果进程仍持有已删除文件的描述符,目录项消失并不意味着对象立即释放。/proc/PID/fd 中带 (deleted) 的链接可帮助发现这种持有;正确做法是让日志组件关闭并重新打开目标文件,而不是直接终止无关进程。

容器里为什么看不到宿主机进程的 fd?

PID 命名空间、procfs 挂载方式和访问权限会限制可见范围。应在与目标进程相同的 PID 命名空间内观察,或通过受控的宿主机诊断通道读取,不要为了排查把容器长期改成过度特权模式。

小结

句柄泄漏的有效排查顺序是:先确认 /proc/PID/fd 的基线持续上涨,再按符号链接目标聚类,随后用 fdinfo 的 inode、flags、mnt_id 等元数据缩小资源簇,最后把资源簇映射到真正拥有生命周期的模块。limits 与 file-max 用来判断容量余量,而不是掩盖未关闭资源。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
实现带字段脱敏与级别过滤的自定义 Handler实现带字段脱敏与级别过滤的自定义 Handler
上一篇
实现带字段脱敏与级别过滤的自定义 Handler
日志量过大时先调级别还是做采样,取舍依据是什么
下一篇
日志量过大时先调级别还是做采样,取舍依据是什么
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    365次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    420次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    435次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    387次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    214次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码