发布时间:2026-09-22 12:30 更新时间:2026-09-22 12:30 阅读量:0
不少站长遇到过这种情况:网站响应变慢,top看着负载不高,iostat -x 1 里 %util 也不算夸张,可磁盘就是咔咔响,业务间歇性卡顿。更让人困惑的是,明明感觉IO很重,iostat 的 r/s、w/s 却只有几十,看不出谁在读写。这种「IO高但指标对不上」的现象,通常不是你眼花,而是观测口径、进程归属和内核回写机制三件事没对齐。下面从进程定位讲到限速与内核参数,给出一套可落地的排查思路。
iostat 统计的是块设备层的吞吐与排队情况,它按设备汇总,不区分进程。当多个进程各自做小量随机读写时,单看设备聚合值可能并不高,但每个请求的等待时间(await)已经拉长。另外有两种常见「隐身」情况:一是IO发生在容器或 cgroup 内部,宿主机 iostat 只看到总量,归属被抹平;二是大量写操作先落在 page cache,由内核 delayed writeback 异步刷盘,应用侧早就返回,磁盘压力滞后出现,时间点对不上。
所以第一步不是继续盯 iostat,而是换一个「按进程」的视角。确认系统里装有 sysstat 与 iotop(各发行版包名略有差异,具体以官方仓库为准):
# Debian/Ubuntu 系
apt-get install -y sysstat iotop
RHEL/CentOS 系
yum install -y sysstat iotop
按进程看实时读写,-o 只显示有IO的进程,-b 批处理便于记录
iotop -o -b -n 5 -d 1
如果 iotop 显示权限不足或读不到数据,多半是内核参数与权限问题,可以临时用 root 运行;容器里跑 iotop 往往只能看到本命名空间内的进程,需要进到对应容器或宿主上用 cgroup 视角观察。
想看得更细,可以用 pidstat 按进程统计,它比 iotop 更适合长期采样和写进报表:
# 每2秒采样一次,共30次,输出各进程的读写速率
pidstat -d 2 30
只看某个可疑进程,比如 PID 1234
pidstat -d -p 1234 1
定位到具体进程后,再去看它在读什么、写什么。可以用 lsof -p PID 查看打开的文件,或者 cat /proc/PID/io 看累计读写字节数,前后对比两次即可判断增长速率。注意 /proc/PID/io 里的 read_bytes、write_bytes 是实际触发块设备IO的量,和进程自报的读写量可能不同,这正是判断「是否在刷脏页」的关键点。
找到进程之后,如果它不能停(比如备份、日志采集、搜索索引),合理做法是限速而不是杀进程。cgroup v1 的 blkio 子系统支持按设备限流,语法是「主设备号:次设备号 速率」。先看设备号:
lsblk -o NAME,MAJ:MIN,SIZE,MOUNTPOINT
假设数据盘为 sdb,对应 8:16
以 cgroup v1 为例,创建分组并设置每秒读写上限(单位 bytes/s):
mkdir -p /sys/fs/cgroup/blkio/backup
限制在 sdb 上读 20MB/s、写 10MB/s
echo "8:16 rbps=20971520" > /sys/fs/cgroup/blkio/backup/blkio.throttle.read_bps_device
echo "8:16 wbps=10485760" > /sys/fs/cgroup/blkio/backup/blkio.throttle.write_bps_device
把目标进程加入该分组
echo 1234 > /sys/fs/cgroup/blkio/backup/tasks
如果用 cgroup v2,路径换到 /sys/fs/cgroup/ 下,用 io.max 控制,格式为「主:次 rbps=… wbps=…」。两种版本字段名与可调项不同,具体以当前内核文档为准。设置后建议用 iotop 或 pidstat 复测,确认速率确实被压住,同时观察业务是否恢复正常。
需要提醒的是,限速只解决「谁占用多少」,不解决「为什么会有这么多写」。如果被限速的是日志或临时文件写入,先考虑日志轮转、降低写入频率,否则只是把压力往后推。
很多「iostat 看不出来」的元凶是 page cache 回写。应用写完就返回,脏页攒到阈值后内核集中刷盘,此时磁盘 util 瞬间拉满,而进程侧早已没有IO动作。可以观察 /proc/meminfo 里的 Dirty 与 Writeback:
grep -E "Dirty|Writeback" /proc/meminfo
查看当前回写相关参数
sysctl vm.dirty_ratio vm.dirty_background_ratio vm.dirty_expire_centisecs
常见调优方向是让后台回写更早、更平滑地启动,避免脏页积累到高位后一次性冲刷。例如:
# 后台回写阈值降到内存的5%,前台阻塞阈值设为10%
sysctl -w vm.dirty_background_ratio=5
sysctl -w vm.dirty_ratio=10
脏页最多存在30秒就被回写(单位百分之一秒)
sysctl -w vm.dirty_expire_centisecs=3000
这些取值不是标准答案,内存大、写密集的机器可以调低比例,让回写更频繁但单次更小;数据库类应用通常建议由数据库自身控制刷盘节奏,随意调内核参数可能影响一致性,改前请评估并做好回滚。持久化写入 /etc/sysctl.d/ 下的配置文件,重启后才会保留。
排查顺序可以固化下来:先用 pidstat/iotop 找到进程,再用 /proc/PID/io 判断是真实IO还是脏页滞后,能限速的用 blkio 限速,属于回写节奏问题的再动 sysctl,最后观察业务延迟是否下降。任何参数调整都建议一次只改一项,配合监控对比效果,避免多变量同时变化后无法归因。
| 📑 | 📅 |
|---|---|
| 服务器被DDoS打满带宽:自查连接分布与限速缓解 | 2026-09-22 |
| Nginx 代理 WebSocket 频繁断连:Upgrade 头、超时与保活配置 | 2026-09-22 |
| systemd timer 替代 crontab 实战:OnCalendar、随机延迟与失败重试 | 2026-09-22 |
| Docker容器时间漂移与crond定时任务错乱:TZ与宿主时间源协同 | 2026-09-21 |
| MySQL 授权与远程访问配置实战:user@host 匹配规则、bind-address 与防火墙三层放行检查 | 2026-09-21 |
| Nginx WebSocket 与 SSE 并存:proxy_buffering 冲突排查 | 2026-09-22 |
| MySQL备份文件损坏怎么验证:一致性校验与恢复演练 | 2026-09-22 |
| Linux 改完 fstab 重启起不来:UUID 混用与救援恢复 | 2026-09-22 |
| Nginx 反代下 Cookie 域与 Path 错乱排查实战 | 2026-09-23 |
| MySQL单表过亿后的分页优化:延迟关联与覆盖索引 | 2026-09-23 |