磁盘 IO 持续 100% 打满
01 前置理论与核心机制¶
1. 磁盘 util 100% 的本质¶
- 利用率 (util) 与吞吐量的区别:监控报警 util: 100% 指的是磁盘设备的“繁忙时间占比”达到 100%。这并不意味着磁盘的读写速度(MB/s)已经达到了硬件极限。大量的随机小文件读写(高 IOPS)同样会耗尽磁盘寻道时间,导致 util 打满,但实际吞吐量可能很低。
- 对系统的影响:当磁盘 IO 达到瓶颈时,会引发系统级连锁反应。CPU 的 iowait(%wa)指标会飙升,依赖磁盘读写的进程会被阻塞,进入 D 状态(Uninterruptible Sleep,不可中断睡眠),导致命令行卡顿、服务响应超时,甚至引发雪崩。
2. 定位问题的两个核心维度¶
排查 IO 打满只需顺着一条线索顺藤摸瓜:
- 第一维度(谁在写):找出当前系统中吃满 IO 的具体进程(PID)。
- 第二维度(写什么):找出该进程正在向文件系统中的哪一个具体路径(FD/文件)进行高频写入。
02 故障排查与恢复闭环 SOP¶
1. 现象确认与宏观定性¶
2. 现场排查链路(抓出进程与真实文件)¶
在命令行出现卡顿、磁盘持续打满的情况下,按以下步骤快速取证:
步骤一:揪出元凶进程(定位 PID)
使用 iotop 工具找出正在疯狂消耗 IO 的进程。 注意:加 -o 会过滤掉空闲进程,只显示正在产生 IO 的进程;加 -P 只显示进程而不显示线程。
- 判定与记录:看最顶上的一行,重点关注 DISK WRITE(或 READ)飙得最高的那一项,记下左边对应的进程号
(例如 42527)。
步骤二:锁定读写文件(查看已打开的文件句柄)拿到进程号后,直接看这个进程打开了哪些文件:
- 排查焦点:输出会有很多行,可以直接忽略上方映射的 .so 系统动态库文件。重点查看 FD(文件描述符)列中带有 w(写入模式,如 3w, 4w)的行,顺着看最后侧的 NAME 真实文件路径,通常能直接看到类似 /tmp/xxx 或 /logs/xxx 的目标文件。
步骤三:实时追踪系统调用(抓取现行)如果 lsof 看到的文件太多,或者想看它实时的写入频率,使用 strace 抓取现行:
- 排查焦点:屏幕上会实时滚动该进程的 write 系统调用。观察它正在往哪个文件句柄(如句柄 3)里疯狂写数据,以及每次写入的块大小。对照刚才 lsof 里查出的 3w 对应的路径,证据链即可完全闭环。
3. 根因修复与防御配置¶
定位到具体文件和进程后,根据业务逻辑进行止损:
- 限流与分离:如果是正常的离线跑批业务,必须在代码层面做写入限流(如 Thread.sleep 或限制吞吐带宽),或者将数据导出盘与核心业务运行盘进行物理隔离。
- 临时阻断:如果是死循环等恶性 Bug 且已影响核心业务,应立即向业务方通报并执行 kill -9
03 真实生产故障现象与案例库¶
案例 1:死循环或异常导致的日志打爆 (logs 目录)¶
- 监控现象:监控显示 IO util 100%,对应服务的 API 响应出现大面积 503 甚至连接超时。
- 排查结果:通过 lsof -p
发现进程的写入目标(带 w 的句柄)全部指向业务的 app-error.log 或 catalina.out 目录。 - 根因与处置:代码中出现了死循环疯狂打印日志,或者上游发起了大批量非法请求导致网关/业务层不断抛出异常而未做日志限流。处置方案为临时调高日志输出级别(如 INFO 改为 ERROR),修改代码增加限流机制。
案例 2:未经分页的全库导出 (tmp 目录)¶
- 监控现象:夜间或运营操作高峰期,磁盘 IO 瞬间打满,且磁盘可用空间在短时间内急剧下降。
- 排查结果:通过 strace -p
-e trace=write 观察到极高频的大数据块写入,结合 lsof 定位到路径为 /tmp/export_xxxx.csv 的临时大文件。 - 根因与处置:运营系统代码里在做未经分页的整库导出,或是批量报表生成,将大量数据持续落盘到临时目录。处置方案为中断导出进程,由研发将同步全量导出改造为异步分页生成或直接对接大数据离线平台。
案例 3:内存不足引发的 Swap 颠簸¶
- 监控现象:磁盘频繁读写,但 iotop 发现排名第一的不是业务进程,而是 kswapd0(内核负责页面置换的线程)。
- 排查结果:通过 free -m 确认物理内存几乎耗尽,Swap 交换区使用率极高。
- 根因与处置:系统物理内存不足,导致内核拼命将部分内存数据写到磁盘的 Swap 空间,稍后又读回,形成“颠簸(Thrashing)”,表现为 IO 打满。处置方案并非优化磁盘,而是排查内存消耗最大的进程(如 Java OOM 倾向),并考虑临时调小 vm.swappiness。
04 生产排障速查表(磁盘 IO 打满)¶
遇到磁盘 IO util 100% 告警,请按序单向排查:顺序排查层级执行命令 / 观测点异常基线与定性处置动作01全局定性iostat -x 1 3 查看设备某设备 %util 近 100%,CPU %iowait 飙高确认发生真实 IO 瓶颈02揪出元凶iotop -oP 观察实时排榜某业务 PID 的 DISK WRITE 长期占据榜首记下对应进程的 PID 号03锁定文件lsof -p <pid> 重点看 FD大量指向 /logs/ 或 /tmp/ 的写句柄 (w)找出高频写入的具体目标文件04抓取现行strace -p <pid> -e trace=write屏幕疯狂滚动针对特定句柄的 write 调用结合 lsof 映射关系坐实元凶05逻辑分析对比路径与业务逻辑logs 多为异常死循环,tmp 多为数据导出联络研发确认是否为预期行为06应急止血kill 或 转移目录故障扩散且严重影响同节点核心业务杀掉挂死进程,或隔离异常输出盘
05 面试应答参考话术¶
面试官问:“如果监控群突然报警,某台机器的磁盘 IO 打满(util 持续 100%),你怎么快速定位是哪个进程在搞鬼?又在动哪个文件?”
可以这样回答:遇到磁盘 IO 打满,命令行通常会有点卡。我的排查思路:先定位哪个进程吃满了 IO,再找出它到底在读写什么文件。第一步:揪出元凶进程
我会直接用iotop -oP 命令。加 -o 会过滤掉没动静的空闲进程,只显示正在产生 IO 的进程。看屏幕最顶上的一行,DISK WRITE 或者 READ 飙得最高的就是元凶,这时候直接把左边对应的 PID 记下来。第二步:锁定目标文件
拿到进程号后,接着找具体的文件。我会用 lsof -p <pid> 看看这个进程打开了哪些文件。输出会有很多行,上面的 .so 系统动态库直接忽略,重点看 FD(文件描述符)带有 w 的写入模式的行,再看最后指向的文件路径,通常能直接发现类似 logs 或者 tmp 目录下的目标文件。第三步:抓现行与闭环取证
如果想抓现行,看看它这一秒到底在往哪写,我会用 strace -p <pid> -e trace=write 实时追踪它的系统调用。立马就能看到它正在往比如 3 号句柄里疯狂写数据。对照刚才 lsof 里的 3w,直接坐实就是那个文件。最后:针对性分析
定位到具体文件,原因基本就清楚了:如果路径是在 logs 目录下,通常是日志打爆了,比如死循环打日志,或者大批量抛异常没做限流;如果路径是在 tmp 目录下,大概率是代码里在做未经分页的整库报表导出,或者在持续生成临时大文件。现场抓准了,拿着进程和文件名去找开发确认,该限流限流,该中断中断。
06 面试高频追问与应答预案¶
追问 1:如果机器上没有安装 iotop(且没外网无法 yum install),怎么看哪个进程占用 IO?¶
- 回答要点:
- 可以使用 pidstat -d 1。pidstat 是 sysstat 包自带的基础工具,几乎所有生产环境标配。
- 运行后查看 kB_rd/s(每秒读取)和 kB_wr/s(每秒写入)两列,同样能清晰找出排名第一的 PID。
追问 2:为什么 IO util 达到了 100%,但我看磁盘的写入速度才几 MB/s,完全没到 SSD 的极限?¶
- 回答要点:
- 这是一个经典的误区。util 表示的是磁盘处于处理 IO 请求状态的时间百分比(繁忙度),而不是带宽。
- 如果程序在进行极其密集的小文件随机读写,磁盘的 IOPS(每秒读写次数)会被迅速打满。磁头或控制器的寻道与响应时间占满了这 1 秒钟,导致 util 100%,但此时传输的总数据量(带宽)可能非常小。
追问 3:排查时用 strace 追踪进程,有没有什么风险?¶
- 回答要点:
- 有一定风险。strace 通过 ptrace 机制挂载到目标进程上,会极大地增加该进程的系统调用开销(尤其是在本来 IO 就极高的情况下),可能导致目标进程响应严重卡顿甚至假死。
- 生产环境应急时,通常仅追踪几秒钟(Ctrl+C 迅速终止),或者精准只追踪特定的系统调用(如 -e trace=write),绝对不能长时间挂载在核心交易进程上。
07 一键巡检脚本(check_disk_io.sh)¶
该脚本用于只读快速巡检宿主机的 IO 整体状态,并自动探测当前读写负载最高的 Top 3 进程。
#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: check_disk_io.sh
# 功能描述: 节点级磁盘 IO 状态巡检与高频读写进程探测 (纯只读)
# 使用方式: 在 Linux/K8S Node 节点以 root 权限执行
# ==============================================================================
set -o pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PLAIN='\033[0m'
echo -e "${BLUE}================================================================${PLAIN}"
echo -e "${BLUE} 磁盘 IO 与阻塞状态快速巡检 ${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"
# 1. 检查整体 CPU iowait 水位
echo -e "\n${YELLOW}[1/3] 检查系统全局 CPU iowait 状态...${PLAIN}"
if command -v vmstat &>/dev/null; then
# 获取平均 iowait (wa列)
IOWAIT=$(vmstat 1 2 | tail -n 1 | awk '{print $16}')
if [ "$(awk -v wa="$IOWAIT" 'BEGIN{print (wa > 15.0) ? 1 : 0}')" -eq 1 ]; then
echo -e "${RED}[WARN] CPU iowait 高达 ${IOWAIT}\%,系统因等待 IO 导致阻塞严重!${PLAIN}"
else
echo -e "${GREEN}[OK] 当前 CPU iowait 为 ${IOWAIT}\%,处于健康范围。${PLAIN}"
fi
else
echo -e "${YELLOW}[INFO] 未找到 vmstat 命令,跳过 iowait 检查。${PLAIN}"
fi
# 2. 检查块设备 util 利用率
echo -e "\n${YELLOW}[2/3] 检查底层块设备繁忙度 (util \%)...${PLAIN}"
if command -v iostat &>/dev/null; then
# 抓取 util 大于 80% 的磁盘设备
BUSY_DISKS=$(iostat -x 1 2 | awk 'NR>3 && $NF ~ /^[0-9]/ &&$NF > 80 {print $1,$NF"%"}')
if [ -n "$BUSY_DISKS" ]; then
echo -e "${RED}[WARN] 发现以下磁盘设备 util 利用率异常偏高 (接近或达到瓶颈):${PLAIN}"
echo "$BUSY_DISKS" | column -t
else
echo -e "${GREEN}[OK] 所有磁盘设备 util 繁忙度正常 (/dev/null; then
echo -e "${BLUE} 进程号(PID) 每秒读取(kB_rd/s) 每秒写入(kB_wr/s) 进程命令(Command) ${PLAIN}"
# 取一秒内的 IO 增量数据,按写入量倒序,排除表头,取前 3 名
pidstat -d 1 1 | awk 'NR>3 && $4 ~ /^[0-9]/ {print $0}' | sort -k5 -n -r | head -n 3 | awk '{printf " %-13s %-19s %-19s %s\n", $3, $4, $5, $8}'
else
if command -v iotop &>/dev/null; then
echo -e "${BLUE} 检测到 iotop,输出当前写入最高的进程:${PLAIN}"
iotop -b -n 1 -o -P | head -n 10
else
echo -e "${YELLOW}[INFO] 宿主机无 pidstat 和 iotop 工具,无法输出 Top IO 进程详情。建议安装 sysstat 包。${PLAIN}"
fi
fi
echo -e "\n${BLUE}================================================================${PLAIN}"
echo -e "${BLUE}巡检完毕。若定位到异常高频写入 PID,请结合 lsof -p 锁定具体目标文件。${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"