半夜两点被值班电话吵醒说一台核心业务服务器卡得像慢动作回放SSH登上去按一个键要等十几秒才有回显。挂掉电话我第一反应是CPU被打满了可上去一看top里CPU总占用还不到30%内存也富余磁盘容量才用了七成。常规指标全都正常业务却实实在在卡了一整天。我按照性能排查的老套路折腾了整整三天最后把根因翻出来的时候自己都愣了几秒不是进程霸占CPU不是内存泄漏不是磁盘坏道而是一个绝大多数运维会下意识忽略的文件系统指标。这篇就把这三天的完整排查过程和最后的真相拆开讲清楚给所有经常跟服务器卡顿打交道的朋友做个参考也帮大家省掉三天。1. 三天排查时间线从“先看看CPU和内存”到一条find命令1.1 第一天常规四件套全部正常问题却真实存在先交代一下现场环境。出问题的是内部OA系统的一台Linux服务器跑了Nginx、PHP-FPM、MySQL硬件配置不算差8核16G磁盘用的是SSD系统盘和数据盘分开挂载。业务量谈不上多大白天高峰期在线用户也就几百人正常情况下这台机器跑得相当轻松。故障现象是这样的HTTP接口响应变得极慢原来几百毫秒能返回的请求高峰期直接飙到十几秒甚至超时SSH登录要等很久在服务器上执行ls、grep这种简单命令也能感觉到明显延迟。但好的一点是服务没有完全挂掉进程都活着端口也在监听只是“活着但不干活”。第一天的排查思路很标准就是大家平时最常用的四板斧uptime top free -h df -h结果非常让人困惑。uptime显示load average在20到30之间徘徊8核机器这个负载已经不低但top里所有进程的CPU占用加起来却不到30%CPU大部分时间在idle。内存用了40%左右没有swap抖动。df -h显示磁盘整体才用了67%怎么看都不像有空间压力。我甚至还跑到业务容器里和数据库上看了半天慢查询但慢查询日志里也没有特别异常的SQL。当时我的判断是“业务高峰期流量抖动”于是重启了Nginx和PHP-FPM。重启完确实好了大概一个小时然后就又卡回原样。那晚基本就在“怀疑某个进程——排查——没结果——重启服务”的循环里过去了。1.2 第二天D状态进程出现排查焦点转向IO第二天白天业务还在继续受影响我开始觉得这不是普通的资源瓶颈。重新打开top盯着看了一会儿发现一个不对劲的地方load average高但CPU的us和sy都不高waIO等待也不算极端按理说这个组合非常少见。于是我用vmstat拉了一组数据vmstat 1 5输出大概是这样的procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 2 0 240000 180000 6800000 0 0 10 30 500 900 5 8 80 7 0r列不高说明CPU运行队列里并没有多少任务在排队但b列不为零表示有进程阻塞在IO上。这个组合指向了一个经典场景大量进程卡在不可中断睡眠状态也就是D状态。我马上用下面这条命令确认ps -eo state,pid,cmd | awk $1D {print $0}果然刷出来一大批D状态的PHP-FPM和MySQL进程。进程只要进了D状态你kill不掉它也没法拉起来继续执行只能在内核态等某个IO操作完成。大量进程堵在这里load average自然飙升但CPU确实没多少活干。D状态意味着IO有问题。可是当我用iostat -x 1看磁盘时又出现了矛盾Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sda 0.00 5.00 0.50 12.00 10.00 80.00 15.00 0.10 8.00 6.00 8.20 0.90 1.20磁盘利用率和IO队列都很低svctm也正常说明磁盘本身并不忙。这就很诡异了大量进程阻塞在IO上磁盘却闲得要命。我甚至怀疑是磁盘固件或硬盘控制器出了问题看了dmesg没有IO错误顺手做了smartctl健康检查也是PASS。1.3 第三天偶发的一次写文件直接暴露了真相第三天上午我准备写个临时脚本来收集系统状态。我在/tmp下执行vim check.sh按了一下i准备进入编辑模式结果VIM直接报错No space left on device看到这个错误我第一反应是“磁盘满了”于是又确认了一遍df -h可用空间明明还有几十GB。刚要骂VIM神经病脑子里突然闪过一个被我忽略太久的可能性立刻敲下了命令df -i看到输出的一瞬间三天来所有的矛盾全都解释通了Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda2 3276800 3276800 0 100% /Inodes用满了。根分区的inode数量是327万全部耗尽一个不剩。系统不是没有磁盘空间而是没有任何空闲的文件节点来创建新文件了。不管是新建文件、创建临时文件、写日志统统失败。那些PHP-FPM进程拿不到新的session文件和临时文件只能在内核里反复重试表现得就像IO阻塞但实际上系统真正的瓶颈是文件系统元数据资源枯竭。接着我按目录统计文件数把范围一步步缩小最终在/var/lib/php/session下看到了触目惊心的一幕find /var/lib/php/session -type f | wc -l # 输出3260000三百多万个session_开头的PHP会话文件堆在那里把inode彻底吃光了。因为会话清理机制一直没有触发这些文件只增不减最终在三天前某个时间点达到了系统上限。2. 为什么常规指标显示正常服务器却越来越卡2.1 load average与CPU使用率严重背离根源在于D状态很多人习惯把load average等同于CPU负载实际上这个值统计的指标比CPU占用更宽。Linux的load average统计的是运行队列中的可运行进程数加上不可中断睡眠进程数。可运行进程是要抢CPU的而不可中断睡眠进程是在等待内核IO操作完成它们根本不占用CPU。当服务器出现大量D状态进程时load average会被这些“等待者”推高但CPU使用率却可能很低。我这次遇到的情况就是典型的阻塞型负载进程都在等文件系统分配inode、写session文件、创建临时文件每一步都失败并重试最终在内核态卡住。这完美解释了为什么8核机器load average冲到20多CPU却只有30%不到。如果只盯着top里的CPU百分比很容易误判成“CPU根本没瓶颈是不是网络问题”从而把排查方向带偏。我第一天的失误就在这里。2.2 磁盘IO指标正常为什么进程还会大面积D状态D状态通常由磁盘IO引起这是排查时的直觉。但Inode耗尽引发的D状态并不走数据块读写这个路径。进程在尝试创建文件或打开文件时需要先向文件系统申请一个空闲inode该操作涉及的是元数据分配逻辑。当inode池空的时候文件系统没有多余的节点可以分配进程的创建操作无法完成。如果业务代码没有做超时容错进程就会长期停留在不可中断的等待状态。而iostat统计的是实际发生的数据块读写和IO设备利用率这类元数据分配失败的操作根本不会产生多少数据块IO所以磁盘util自然上不去。用个更生活化的比喻高速公路单向八车道全空着磁盘IO不忙但唯一的高速收费站关闭了inode耗尽所有车都被堵在匝道上谁也过不去。你只看高速路上的车流量会觉得路况好得很但所有司机的实际体感就是“被堵死了”。2.3 网络与内核日志排查正常某种程度上是误导第二天后半段我还花了不少时间在网络层。当时想的是“磁盘IO不忙但进程大面阻塞会不会是网络栈问题”。结果sar -n DEV看网卡流量没有异常ss -s看socket连接数也没有暴涨dmesg里没有OOM、没有网卡重置、没有文件系统报错一切干净得让人绝望。内核日志干净这一点恰恰是这类故障最坑的地方。inode耗尽不会直接打印到dmesg它不是硬件故障也构不成内核panic级别的错误。服务器表现出来的只有业务卡顿和进程阻塞所有底层日志都安静如常。如果排查者依赖日志输出作为线索很容易绕进死胡同。3. 根因解析inode耗尽一个df -h永远查不出的问题3.1 容量与文件节点一张磁盘上的两套系统要彻底理解这个坑需要把文件系统的两个资源分开看。df -h统计的是数据块的占用情况也就是磁盘上真正存储文件内容的那些空间。而inode是文件系统在建盘时预先分配好的元数据结构每个文件、目录、符号链接至少要占用一个inode用来记录文件的权限、属主、大小、数据块位置等核心信息。也就是说一块磁盘上并行存在两套“仓库”一套存内容一套存档案。df -h管前者df -i管后者。很多时候大家只盯前者后者完全没概念。默认情况下ext4文件系统会按固定的比例分配inode数量通常每16KB数据空间分配一个inode。对于一块1TB的磁盘大约会生成六千多万个inode。看起来很多但遇到海量小文件的场景比如PHP的session文件、邮件队列积压、日志拆分成几千个文件几百万个文件就能轻松吃光。我在现场用df -i看到IUse100%时df -h的数值其实是67%。整整两天我看到的都是这个“健康值”完全没有想过去看另一套指标。复盘时觉得这个认知盲区才是整件事里最贵的一课。3.2 小文件是怎么在三天内堆满三百多万个inode的PHP-FPM默认的session存储方式是文件存储默认路径在/var/lib/php/session。每个用户会话对应一个以sess_开头的独立文件。用户量一大session文件数量就直线增长。正常情况下PHP的session GC机制会定期清理过期session文件。但它是一个概率性触发机制不是定时器受session.gc_probability和session.gc_divisor两个参数控制默认分别是1和100也就是说每次session请求发起时有1%的概率触发GC。如果某个请求在GC执行前就结束了那么这次清理就不会发生如果配置被改成0GC直接永久停用。这台服务器的session.gc_probability被改成了0等于清理机制被手工关掉了。后果就是session文件只增不减像下水道只进不出时间一长必然堆满。平时每天几百个用户只是温水煮青蛙等到了某一天文件数悄悄越过inode上限故障就突然爆发了。而且这类session文件极小平均一个才几KB三百多万个文件加起来也只占几个GB的磁盘空间。对df -h来说完全不痛不痒对df -i来说却是灭顶之灾。3.3 inode耗尽后服务器为什么会呈现“假死”状态inode耗尽之后系统里任何需要创建新文件的操作都会失败PHP-FPM写不了新的session文件MySQL做排序可能要写临时表文件日志组件写不了新日志用户上传文件也传不上去。这些操作大面积失败后缺乏容错处理的进程就会卡死。表现到用户侧就是接口超时、网页打不开、上传一直转圈。表现到服务器侧就是D状态进程暴涨、load average飙升但CPU、内存、磁盘IO、网络全部正常。很多人会误以为这是“玄学问题”或者“硬件偶发故障”实际上系统只是被一个小得不能再小的文件系统资源卡住了脖子。之所以重启服务能短暂缓解是因为重启后进程内残留的句柄和队列被重置部分连接请求能恢复处理。但inode耗尽这个根因没有解除新进程很快又会因为创建session文件失败而重新陷入阻塞。所以重启只是“麻醉剂”不是“手术”。4. 快速定位“文件堆满者”的命令组合4.1 第一步永远是用df -i确认文件系统边界遇到任何无缘无故的卡顿尤其是load average高但CPU不高的场景建议第一件事就是把df -h和df -i一起跑。前者看容量后者看inode两个都看一眼最多花三秒钟但能过滤掉一大类隐蔽故障。df -h df -i如果df -i里出现IUse接近100%的分区基本就可以锁定方向了。还可以用tune2fs查看文件系统的inode总量及预留信息tune2fs -l /dev/sda2 | grep -i inode4.2 第二步按目录统计文件数逐层缩小范围确认inode耗尽之后接下来要回答一个问题哪个目录塞了几百万个文件有个土办法非常有效就是按一级目录统计文件数量然后顺着排序结果逐层往下钻。for dir in /var/lib/*; do count$(find $dir -type f 2/dev/null | wc -l); echo $count $dir; done | sort -rn | head -20排在最前面的自然就是嫌疑最大的目录。然后进入该目录继续统计下一级子目录for dir in /var/lib/php/*; do count$(find $dir -type f 2/dev/null | wc -l); echo $count $dir; done | sort -rn | head这套方法不需要任何额外工具纯靠find加wc -l就能工作。文件数量特别多的时候find本身可能会跑得略慢但只要给点耐心总能出结果。4.3 第三步常见的“小文件重灾区”黑名单根据我这几年的排查经验有几个位置出现海量小文件的概率特别高遇到类似卡顿可以优先检查路径常见原因/var/lib/php/sessionPHP会话文件未清理/tmp、/var/tmp程序临时文件无清理策略/var/spool/postfix邮件投递失败积压/var/log/journalsystemd日志无轮转/data/app/upload/tmp应用上传临时目录/var/cache缓存文件未生效/未清理如果是云主机或者容器环境Docker默认的overlay2目录也可能因为镜像层、容器日志积累大量小文件也需要一并考虑。4.4 一个特别提示千万别直接在inode满的分区上乱跑全盘扫描inode已经100%的情况下全盘find是有代价的。文件数量极大时目录项锁竞争会让系统变得更卡甚至让故障雪上加霜。稳妥做法是先看系统路径、应用配置、业务日志里跟文件路径相关的线索快速定位到最可疑的目录再在那个目录范围里做统计而不是一上来就跑find / -xdev。我这次因为已经通过df -i确认了是根分区inode耗尽随后又通过应用session机制大致猜到了方向所以是直接跳到了/var/lib/php/session这个具体位置去验证中间的弯路少了很多。5. 应急清理与根治方案5.1 先止血小心地把最老的会话文件清掉确认了三百多万个session文件之后第一目标是快速释放部分inode让系统恢复正常的文件创建能力。但清理操作不能莽尤其不能误删当前在线用户的session否则会导致用户集体掉线。我的处理思路是只清理一定时间以前没有被修改过的文件。session文件只要用户活跃mtime就会持续更新超过一定时间没动的基本可以判定为过期会话。find /var/lib/php/session -type f -mmin 1440 -delete这条命令删除1440分钟24小时之前最后修改的session文件。执行时因为文件数量巨大整个过程会比较慢需要等待一段时间才能看到df -i的数值明显下降。当时我执行完大概等了十几分钟inode使用率从100%降到92%左右服务器的D状态进程开始肉眼可见地消退业务响应也逐步恢复了正常。在生产环境删除文件前建议先跑一遍不带-delete的查询确认匹配到的文件确实是可清理对象find /var/lib/php/session -type f -mmin 1440 | wc -l5.2 堵住增量恢复PHP会话GC机制止血之后必须解决“还会再满”的问题。这台服务器的PHP配置里session.gc_probability被改成了0我直接把它恢复成默认的1并确认session.gc_divisor为100这样每次session请求有1%的概率触发过期清理。同时建议在crontab里增加一个兜底任务定期清理超过保留期限的session文件防御GC机制失效的极端情况0 3 * * * find /var/lib/php/session -type f -mmin 1440 -delete这个任务放在凌晨低峰期执行清理30天前的过期session文件。保留期限可以根据业务会话的最长有效时间灵活调整比单纯依赖PHP的随机GC要可靠得多。5.3 建立一个inode监控项把df -i加进监控系统这次故障还有一个更深层的教训我们的监控全部是针对磁盘容量的没有任何一项针对inode。Prometheus的node_exporter其实自带filesystem指标其中node_filesystem_files_free和node_filesystem_files就对应inode的剩余量和总量。只要在告警规则里加一条低于某阈值就报警的规则这类问题基本可以在初露苗头的阶段就被发现。- alert: InodeExhausted expr: node_filesystem_files_free{mountpoint/} / node_filesystem_files{mountpoint/} 0.1 for: 10m labels: severity: critical annotations: summary: 根分区inode不足10%Zabbix用户同样可以在模板里添加vfs.fs.inode相关的监控项。把df -i纳入日常巡检是一个投入成本极低、收益极高的改进。5.4 顺手把日志轮转机制也盘一下这类小文件堆积问题通常和日志轮转不到位高度相关。虽然本次根因是PHP session但在同一台机器上检查一圈时我还发现应用日志目录里的*.log.1、*.log.2这种旧文件攒了一大堆只是数量远没到撑爆inode的程度属于潜伏隐患。给日志加上logrotate规则是个标准解法/data/app/logs/*.log { daily rotate 30 compress delaycompress missingok notifempty copytruncate }这样日志文件按天轮转只保留30份压缩后体积可控文件数量也被牢牢限制住。任何写文件的应用都应该考虑“定期轮转数量上限”这两个约束条件。6. 复盘这种“诡异卡顿”为什么能藏三天6.1 最大的盲区不在技术在排查习惯回头看这三天技术上其实没有什么冷门难题每个环节都是教科书里有的内容D状态进程、inode概念、df -i命令。难就难在它不在我熟悉的默认排查路径上。我第一天先看CPU和内存第二天转向磁盘和网络都是在常规四件套里打转直到VIM那个报错把我踢出了惯性思维。事后我总结了一条很实用的排查原则只要load average高而CPU使用率低先看D状态进程再查文件系统元数据资源这两步能过滤掉一大部分“幽灵卡顿”。别再被“磁盘API看起来不忙”骗了inode耗尽这种问题从iostat上几乎看不出任何异常。6.2 监控体系存在惊人的盲区这次故障暴露出我们的监控体系只覆盖了“容量”而没有覆盖“元数据”。磁盘容量监控、内存监控、CPU监控做得再齐全也拦不住inode耗尽这类问题因为那是完全不同的资源维度和指标口径。结合热点话题里频繁出现的各种服务器卡顿现象我强烈建议把所有服务器的df -i加进监控并设置告警。很多卡顿、假死、服务间歇性不可用排查到最后都是文件句柄不够、inode用光、目录锁竞争这一类元数据层面的问题它们在传统监控面板上可能都显示为零。6.3 一些值得固化的经验清单这里把这次的排查血泪直接整理成清单方便以后照着做遇到卡顿先并行看top -c、df -h、df -i三分钟能覆盖掉最常见的几类问题。vmstat看到b列长期非零立刻用ps -eo state,pid,cmd | awk $1D抓D状态进程。D状态进程多但iostat正常优先怀疑文件系统元数据瓶颈inode耗尽、目录饥饿都是典型。清理inode耗尽的目录时先统计文件时间分布只删过期文件别影响活跃业务。任何文件类应用都要配套轮转和清理策略否则时间会帮你攒出一个大故障。监控体系至少要包含容量利用率、inode利用率、关键目录文件数这三个维度缺一不可。每当再遇到半夜被叫起来看服务器卡顿的场合我都会先跑一遍这套清单。那次折腾了三天的经历说到底也值了。排查过程中踩过的弯路有多深对这套方法的信任就有多扎实。你要是也遇到过类似查不出根因的“玄学卡顿”不妨先从df -i开始看。
企业数字化 ERP 产品动态
相关推荐
树莓派驱动ST7735小屏:用Python打造机器人灵动眼睛动画 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 13:27:48
Buck电路尖峰吸收:RC、RCD与TVS实测对比与选型指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 13:27:42
从脚本到配置:用Advanced Installer Architect打造商业级MSI安装包 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 13:27:42
OcctCSharpBridge:.NET 下的 Open CASCADE 封装与 CAD/BIM 开发实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 14:00:51
ESP32开发板换板适配指南:小智源码板级适配实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 14:00:45
Design Compiler:使用read_file命令读取RTL设计 相关阅读
Design Compilerhttps://blog.csdn.net/weixin_45791458/category_12738116.html?spm1001.2014.3001.5482 目录 read_file命令 读取参数化设计 举例说明 等价表示 写在最后 Design Compiler可以使用read_file读取RTL设计(不建议,建议使用anal… · 2026/9/24 14:00:45
中科曙光服务器培训全解析:从硬件选型到系统部署与排障 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 14:00:45
Unity引擎底层揭秘:mono_add_internal_call如何打通C#与C++ 一次很普通的反编译。 你想看看 Transform.position 到底怎么实现的,用 dnSpy 打开 UnityEngine.CoreModule.dll: public Vector3 position
{get{get_position_Injected(out Vector3 result);return result · 2026/9/24 14:00:39
Python | PyCharm一键无脑安装 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 14:00:39
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44