磁盘与 IO 排查:iostat、inode、句柄

磁盘问题有两种“满”,报错几乎一样但处理方式完全不同:空间满了要删文件,inode 满了删小文件才有用。IO 问题则更隐蔽:%util 看着不高,延迟却已经翻十倍。本章把磁盘空间、inode、IO 延迟、文件句柄四条线一次讲清。

第一步:两种“满”必须分开看

df -h      # 空间使用率
df -i      # inode 使用率
情况报错关键字判断命令处置
空间满No space left on devicedf -h 显示 100%清理大文件、扩容、轮转日志
inode 满同样是 No space left on devicedf -h 正常、df -i 显示 100%清理海量小文件(缓存、session、临时文件)
句柄耗尽Too many open fileslsof -p <pid> | wc -l 接近 ulimit -n提高上限并修连接未释放的代码

只看 df -h 是最常见的低级失误:磁盘还有 30% 空间,服务却已经写不进任何文件,原因就是 inode 用完了。

找出到底是谁占的

# 1. 逐层下钻目录占用
du -sh /var/* 2>/dev/null | sort -rh | head -10
du -sh /var/log/* 2>/dev/null | sort -rh | head -10

# 2. 找大文件
find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null | head

# 3. 关键:找出已删除但仍被进程占用的文件(空间不释放的元凶)
lsof +L1 2>/dev/null | head -20
lsof -nP 2>/dev/null | grep -i deleted | awk '{print $1,$2,$7,$9}' | sort -k3 -rn | head

# 4. 找小文件多的目录(inode 满时用)
find /var -xdev -type f 2>/dev/null | awk -F/ '{print $2"/"$3"/"$4}' | sort | uniq -c | sort -rn | head
现象结论处理
du 统计的总量远小于 df 显示有已被删除但仍被进程持有的文件重启或让进程重新打开日志文件,用 truncate 清空而非 rm
日志目录占大头日志未轮转或级别过高立即轮转 + 降级别
大量 .tmp.part 文件上传或任务失败残留清理并加清理任务
容器可写层巨大应用往容器里写数据挂载 volume,别写容器层
inode 满但数据量不大小文件过多(session、缩略图、缓存)删小文件或换文件系统方案

iostat 关键列判读

iostat -x 1 5
# Device  r/s   w/s  rkB/s  wkB/s  r_await w_await await aqu-sz %util
含义判读阈值与结论
r/sw/s每秒读写次数(IOPS)与设备上限对比;HDD 通常几百,SSD 可达数万
rkB/swkB/s每秒读写吞吐(KB)与带宽上限对比,大文件场景看这两列
await平均每次 IO 的总等待时间(毫秒,含排队)明显高于平时即变慢,HDD 常见 < 20ms,SSD 应 < 5ms
r_await / w_await分开看读与写的等待时间写慢常见于 fsync 与脏页回写
aqu-sz平均请求队列长度(旧版本叫 avgqu-sz持续 > 1 说明设备有排队,IO 越积越多
%util设备至少有 1 个请求在处理的时长占比接近 100% 表示忙,但多队列 SSD 上 100% 不等于饱和,要结合 await
%iowait(top/vmstat)CPU 等 IO 的时间占比高即磁盘瓶颈,但虚拟机上会被 steal 干扰

一句话判读法:先看 await 是否升高,再看 aqu-sz 是否排队,最后才看 %util。只盯 %util 会漏掉“队列很长但设备偶尔空闲”的情况。

# 谁在制造 IO:进程级定位
pidstat -d 1 5      # kB_rd/s 与 kB_wr/s,找出读写最多的进程
iotop -oPa          # 交互式,看累计写入量(需 root)

# IO 压力(PSI),比 %util 更贴近“应用是否被拖慢”
cat /proc/pressure/io
# some avg10=12.34 total=... :some 表示至少一个任务等 IO 的时间占比,超过 10% 就明显影响延迟

IO 慢的常见根因

根因特征处置
日志同步写w_await 高,sy 高,栈里是 fsync改异步日志、降级别、分离日志盘
大查询扫盘读 IOPS 高,DB 慢查询日志有记录加索引、加缓存、限制并发
脏页集中回写%util 周期性冲到 100%,延迟尖刺dirty_ratio、提高回写频率
数据盘与日志盘共用任何写入互相干扰物理分离或至少逻辑分离
快照/备份任务每天固定时间点 IO 高峰错峰执行,限制备份速率
云盘 IOPS 额度用尽await 高但 %util 不高升配云盘或降低写入量

句柄与磁盘的联动

句柄泄漏往往同时表现为磁盘问题:文件没关,删除后空间不释放,日志还继续写。

cat /proc/sys/fs/file-nr        # 已分配 / 未使用 / 系统上限
ulimit -n                       # 当前 shell 的软限制
cat /proc/<pid>/limits | grep -i 'open files'
ls /proc/<pid>/fd | wc -l
lsof -p <pid> | wc -l           # 与上限对比,看余量
lsof -nP -p <pid> | grep -c REG # 打开了多少普通文件
现象结论
句柄数随 QPS 线性上涨、回落时不降连接或文件未释放,典型泄漏
句柄数接近 ulimit -n马上会出现 Too many open files,先提上限再修代码
大量 deleted 文件空间不释放,重启该进程或 truncate 释放
file-nr 第一项接近第三项系统级句柄也将耗尽,需调 fs.file-max

处置动作

# 1. 清空正在被写入的日志文件:用 truncate 而不是 rm
truncate -s 0 /var/log/myapp/app.log
: > /var/log/myapp/app.log          # 等价写法

# 2. 立即轮转并压缩
logrotate -f /etc/logrotate.d/myapp

# 3. systemd-journald 占用过大时限制总量
journalctl --vacuum-size=500M

# 4. 清理已删除但仍被占用的空间:重启持有该句柄的进程
lsof +L1 | awk '{print $2}' | sort -u

处置模板:先 truncate 让服务立刻恢复写入,再定位是哪个进程、哪个目录、哪类文件在涨,最后改轮转策略或拆盘,不要只删一次就走。

预防与巡检

  • 空间与 inode 都要设告警,阈值 80% 预警、90% 告警。
  • 日志强制轮转(按大小与天数双条件),并保留压缩归档。
  • 把日志目录与数据目录放在不同分区,避免日志写满拖垮数据库。
  • 关键服务限制单日日志上限,超出直接丢弃并统计,宁可少日志也不要挂服务。
  • 云盘注意 IOPS 与吞吐额度,容量升配不等于性能升配。
  • 巡检脚本每天统计 awaitaqu-sz%util 峰值,形成基线后才能快速判断“这次是否异常”。

小结:df -hdf -i 必须同时看,空间满与 inode 满的报错相同但处置不同;IO 判读按 awaitaqu-sz%util 的顺序看,进程级定位用 pidstat -diotop,压力水平用 /proc/pressure/io;被写入的日志用 truncate 清空而不是 rm,最后把日志与数据分盘、轮转与句柄上限都纳入日常巡检。

笔记加载中…