网站响应突然从几百毫秒变成几秒,CPU利用率却不高,登录服务器后还能看到进程处于D状态。这时磁盘I/O确实值得检查,但监控上的I/O百分比只是入口,不能直接证明硬盘坏了,也不能看到 %util 接近100%就立刻扩容。
磁盘压力可能来自数据库集中刷盘、日志暴涨、备份扫描、内存不足后的交换、容器写层、快照任务,或云盘底层延迟。排查重点是把同一时间窗口里的设备延迟、队列、吞吐、进程和具体文件关联起来,再选择最小改动。
下面以常见Linux云服务器为例。NVMe、本地SSD、网络块存储、RAID、LVM和虚拟机设备名称不同,单项指标的含义也会变化。读取性能数据通常风险较低,但 iotop、lsof 和递归文件扫描在繁忙机器上也有开销,采样范围和频率要受控。

固定故障时间和业务影响
先记录当前时间、时区与用户症状:
date -Is
uptime
确认是所有请求变慢、只有数据库接口变慢,还是SSH输入和系统命令也变慢。应用日志中保存请求ID、接口耗时和数据库耗时。只有页面慢,不能直接把瓶颈定在磁盘,因为DNS、上游接口和锁等待也会造成相似现象。
快速读取CPU等待与进程状态:
vmstat 1 10
重点看 wa、b、si、so。wa 上升表示CPU有时间在等待I/O,但在多核、虚拟化和不同负载下不能用固定阈值判断;b 持续增加说明更多进程不可中断地等待;si、so 活跃则提示内存压力和交换也在制造磁盘请求。
单次采样可能碰巧落在空闲瞬间,至少连续观察10到60秒,并覆盖故障仍在发生的时间。问题已经结束时,优先读取监控、sysstat历史和应用日志,不要通过压测强行重现生产故障。
用iostat找到真正忙的块设备
安装sysstat后执行:
iostat -xz 1 10
第一组通常是系统启动以来的累计平均,后续组才是每秒采样。排查突发故障时应看后续区间,不能拿启动以来的平均值判断当前状态。常用指标包括:
r/s、w/s表示每秒读写请求数,适合观察IOPS方向。rkB/s、wkB/s表示吞吐量,顺序大文件和大量小请求的表现不同。await包含请求排队与设备服务时间,是延迟的重要线索。aqu-sz表示平均队列长度,持续增长通常说明请求进入速度超过处理速度。%util表示设备有I/O请求在处理的时间比例,不能脱离设备并发能力单独使用。
传统单盘上 %util 长期接近100%、await 与队列同时上升,并伴随业务延迟,才形成较完整的饱和证据。NVMe、RAID和云端虚拟块设备能够并行处理多条请求,100%不一定等于已达到全部硬件上限;相反,某些云盘在吞吐或IOPS限额处被节流时,表面带宽并不高,延迟却明显上升。
记录异常设备名称,例如 sda、vda、nvme0n1 或 dm-0。LVM和设备映射环境里可能同时看到逻辑设备与底层设备,不能把同一批I/O重复相加。
把块设备映射到挂载点和业务目录
使用 lsblk 查看层级:
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,PKNAME
findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS
异常设备是 dm-0 时,沿着LVM或加密映射找到对应卷和底层盘。一个物理设备可能承载根目录、Docker和数据库多个逻辑卷,设备忙并不能直接说明哪个业务写入最多。
查看文件系统容量和inode:
df -hT
df -ih
磁盘接近满时,文件分配、日志轮转和数据库临时文件可能表现异常。inode耗尽则是小文件数量问题。容量与性能是两件事,但两者可能同时发生,尤其是日志暴涨场景。
挂载选项也要记录。网络文件系统、FUSE和容器overlay层不一定直接对应本机块设备。df 显示的路径与 iostat 设备需要通过实际部署拓扑关联。
用pidstat和iotop定位进程
按进程采样读写速率:
pidstat -d 1 10
关注 kB_rd/s、kB_wr/s、iodelay 和命令名。持续排名靠前的进程比单次峰值更值得检查。数据库可能把写入交给后台线程,应用进程的逻辑写入和块设备落盘时间也可能错开,因此需要结合后续日志和刷盘指标。
有root权限并已安装iotop时,可短时观察:
sudo iotop -oPa
-o 只显示有I/O的任务,-P 按进程汇总,-a 累计观察期内的I/O。不同iotop版本选项可能不同,先看 iotop --help。长时间运行会持续读取内核统计,故障采样完成后应退出。
高写入来自 jbd2、kworker 或文件系统后台线程时,不要认为内核线程就是业务根因。它们往往在代替某个应用完成日志提交、回写或设备处理,需要继续从脏页、打开文件和应用操作向上追溯。
找到进程正在访问哪些文件
确定PID后读取文件描述符:
sudo lsof -p PID | sed -n '1,120p'
sudo ls -l /proc/PID/fd | sed -n '1,120p'
输出可能包含数据库路径、用户文件、令牌文件和已删除文件名,分享前要脱敏。lsof 在文件描述符很多的进程上可能较慢,先限制单个PID,不要一开始扫描整台机器。
检查进程级I/O累计值:
sudo cat /proc/PID/io
间隔几秒读取两次,可观察 read_bytes、write_bytes 与 cancelled_write_bytes 的变化。它们是累计值,不是即时速率,必须用差值和采样间隔计算。页面缓存会让应用读写调用与物理设备I/O不完全同步,因此不能只凭一个字段下结论。
日志目录、备份目录或容器目录可做受控的一级统计:
sudo du -xhd1 /var/log 2>/dev/null | sort -h
sudo du -xhd1 /var/lib/docker 2>/dev/null | sort -h
繁忙大盘上执行深度递归 du 本身会产生大量读取。先从已知挂载点和一级目录开始,避免在根目录直接全盘扫描。
区分应用写入、缓存回写和交换压力
查看内存与脏页:
free -h
grep -E 'Dirty|Writeback|Cached|Swap' /proc/meminfo
vmstat 1 20
Dirty 持续积累后集中下降,同时设备写入突然升高,可能是内核回写。应用批量生成数据、数据库检查点和日志缓冲都可能造成这种节奏。直接修改 vm.dirty_* 参数会改变整机回写行为,未经压测不要在生产机套用网络教程中的数值。
si、so 持续活跃并伴随内存不足,说明系统在交换页。此时仅限制磁盘写入可能让进程更慢,根因应从内存占用、容器限制、缓存和应用泄漏入手。
数据库要结合自身指标。MySQL可查看缓冲池、脏页、刷盘和慢查询;PostgreSQL可检查检查点、后台写入器、WAL与临时文件。看到数据库进程写盘多并不等于异常,关键是写入量、检查点频率和延迟是否偏离正常基线。
排除日志、备份和容器任务
围绕故障时间检查定时任务与服务日志:
systemctl list-timers --all
sudo journalctl --since '1 hour ago' --no-pager
sudo grep -R '' /etc/cron.d 2>/dev/null | sed -n '1,160p'
备份压缩、日志轮转、数据库导出、镜像拉取和安全扫描常在固定时间触发。不要只因时间重合就认定根因,还要用进程I/O、文件大小变化和任务日志互相验证。
Docker环境查看容器可写层与日志:
docker stats --no-stream
docker system df
docker ps -q | while read id; do docker inspect "$id" --format '{{.Name}} {{.LogPath}}'; done
默认JSON日志未限制大小时,错误循环可能持续写盘。确认单个日志文件后先处理应用错误,再配置受支持的日志轮转。直接执行 docker system prune -a --volumes 可能删除仍有价值的数据卷,不应作为I/O高的通用修复。
检查内核与底层存储错误
读取内核日志:
sudo journalctl -k --since '2 hours ago' --no-pager
sudo dmesg -T | tail -n 200
重点查找I/O error、timeout、reset、blk_update_request、EXT4-fs error、XFS错误、NVMe reset和只读重挂载。出现设备超时或文件系统错误时,继续高强度写入可能扩大损坏,应先保护数据和降低业务写入,再评估快照、故障转移和离线检查。
虚拟机中的SMART信息通常不可见或没有诊断意义,云盘健康和限额要结合服务商控制台指标。确认实例侧没有异常任务,而延迟与云盘节流、突发额度耗尽或平台事件吻合时,再携带设备、时间窗口、iostat和内核日志提交工单。
不要在已挂载且有业务写入的文件系统上直接运行修复命令。fsck 通常需要卸载或进入救援环境,错误使用可能造成数据损失。
用历史数据判断是突发还是长期瓶颈
sysstat已持续采集时,可以查看历史设备数据:
sar -d -p -f /var/log/sysstat/saDD
不同发行版的目录可能是 /var/log/sa/,DD 要替换为日期。历史数据能说明异常从何时开始、持续多久,以及是否每天固定出现。没有提前启用采集就无法还原完整细节,临时安装工具也不会补回过去的数据。
建立基线时至少记录业务高峰与低峰的吞吐、IOPS、await、队列和应用响应时间。固定阈值很容易误报,偏离自身历史基线通常更有价值。
现有机器上的应用、数据库与备份混在同一块盘时,可以在 萤光云 建立同版本的脱敏测试环境,把数据库与日志分别放置后观察差异;也可以使用按小时计费的 LightNode 构造受控读写基线。测试数据规模、文件系统和I/O模式必须接近原场景,单纯跑一次顺序测速不能替代业务验证。
根据证据做最小改动
日志错误循环造成写入,就修复错误源并轮转日志;备份与高峰冲突,就调整时间、限速或拆分设备;交换频繁,就处理内存压力;数据库检查点过密,则按数据库证据调整,而不是先换盘。
设备确实达到IOPS或吞吐上限时,再评估升级云盘、增加并行设备、拆分数据与日志或迁移实例。扩容前要确认新规格改变的是容量、IOPS、吞吐还是延迟,不同产品的性能随容量和实例规格变化。
一次只改一个主要变量,并保存修改前后的同一组指标。直接重启服务器可能让队列清空、缓存重建,短时间看似恢复,却会丢失现场,也不能修复周期任务和容量瓶颈。
验收要覆盖原来的负载窗口
修复后在原本容易出现问题的时段观察设备、进程与业务:
iostat -xz 5 12
pidstat -d 5 12
vmstat 5 12
验收不要求 %util 必须很低,而是请求延迟恢复、await和队列不再持续恶化、错误日志消失,且业务吞吐达到目标。数据库还应核对慢查询、提交延迟和复制状态,不能只看操作系统层。
重启相关服务或等待下一次定时任务再复测,确认日志轮转、资源限制和任务时间都已持久化。最终结论应能回答哪块设备在何时变慢、哪个进程和文件产生请求、底层是否报错,以及修改后哪些指标得到改善。
常见问题
iostat里的%util达到100%就一定要换磁盘吗?
不一定。还要结合await、队列、吞吐、设备类型和业务延迟。NVMe、RAID与云端虚拟设备的并行能力不同,单个百分比不能决定扩容。
CPU不高,为什么网站仍然很慢?
进程可能在等待磁盘、网络或锁。此时CPU没有执行有效工作,利用率不高仍会出现长响应时间,应结合进程状态和等待指标判断。
重启服务器能解决IO高吗?
重启会清空部分队列和缓存,也会终止当时的任务,但周期备份、日志增长、内存不足或存储限额仍会再次触发。应先保留证据再决定操作。


