最让人头疼的不是服务器彻底挂掉,而是它“半死不活”——CPU占用不到10%,内存余量也充足,网络没有波动,但整台机器就像陷在沼泽里一样,SSH敲个命令要好几秒才回显,Web服务偶尔报504。跑top一看,load average已经飙到8以上,底下还蹲着一排状态为D的进程。遇到这种情况,我基本可以锁定问题出在磁盘I/O层。
这篇文章会把我在Ubuntu 22.04上排查磁盘I/O高负载的完整链路写清楚,核心工具是iotop和systemd。iotop负责帮我把“哪个进程在吃磁盘I/O”这个关键问题回答清楚,systemd则承担两个角色:既能用cgroup统计来辅助审计,又能作为限流手术刀直接抑制失控进程的I/O读写。无论你是刚接触Linux运维的新手,还是被这类问题反复折腾过的老手,这条链路都有一定的通用性。
1. 先确认“I/O高”不是错觉:量化问题再动手
很多人一遇到服务器负载高,第一反应就是跑top看CPU,发现CPU不高就开始胡乱怀疑进程,折腾一圈才发现问题在磁盘。所以我的习惯是,先花三分钟把问题量化清楚,确认I/O确实是瓶颈,再决定下一步怎么排查。
1.1 三分钟建立基线:iostat + vmstat 的组合读数
第一条命令,永远先跑iostat -x 1。iostat属于sysstat包,Ubuntu 22.04上如果没装,先apt install sysstat。
bash复制iostat -x 1
这条命令会以1秒为间隔刷新,直接打印每个块设备的详细读写情况。我最关心的几列是:
%util:设备在采样周期内有多忙。接近100%说明设备已经处于饱和状态。await:I/O请求从发出到完成的平均等待时间,包含排队时间和服务时间,单位是毫秒。这个值越高,说明请求排队越严重。rkB/s和wkB/s:每秒读写的KB数,直接反应吞吐量。r_await和w_await:读请求和写请求的平均等待时间,可以区分读慢还是写慢。
第二眼再去看vmstat 1的输出。
bash复制vmstat 1
重点看三个位置:wa列(CPU花在等待I/O上的时间百分比)、bi列(每秒从块设备读入的数据量)、bo列(每秒写入块设备的数据量)。如果wa持续超过30%,配合iostat里的%util偏高,基本可以确认I/O是当前系统的瓶颈。
另外一个值得养成习惯的动作是检查/proc/pressure/io。Ubuntu 22.04的内核默认开启了PSI(Pressure Stall Information),直接读这个文件,能看到系统由于I/O等待造成的任务停滞时间。
bash复制cat /proc/pressure/io
输出里有两个关键指标:some表示至少有一个任务因为等待I/O而停顿的时间,full表示所有任务都被I/O卡住的时间。后面跟着的是过去10秒、60秒、300秒三个时间窗口的累计数值。如果full这个值在10秒窗口里有明显涨幅,说明I/O压力已经大到影响整机响应。
1.2 区分“读写量过大”和“读写路径变慢”
iostat的读数不仅帮你确认问题,还能帮你区分I/O高负载的两种形态。这两种形态的排查方向完全不同。
| 形态 | 典型读数 | 排查方向 |
|---|---|---|
| 读写量过大 | rkB/s、wkB/s很高,%util饱和 |
找出具体进程,查为什么产生这么多读写 |
| 读写路径变慢 | await飙到几百毫秒,但rkB/s、wkB/s并不高,%util可能只有50% |
更可能涉及硬件降级、块层争抢、云盘限制 |
举个实际的例子:一台机器上跑着数据库和Web服务,wkB/s长期保持在200MB/s以上,%util一直是100%,这就是典型的“写量过大”,用iotop去抓谁在写盘就对了。但如果你看到await已经700毫秒,wkB/s却只有5MB/s,那问题多半不在进程的读写行为上,而在存储链路本身——可能是磁盘硬件故障、RAID卡策略、或者云平台底层存储排队,这时候把iotop盯到天荒地老也找不到答案。
1.3 别忽略D状态进程和PSI数据
在确认I/O瓶颈的过程中,有一个经常被忽略的信号:处于不可中断睡眠状态(D状态)的进程。这种进程正在等待I/O完成,无法被信号杀死,也无法被正常调度。如果系统里频繁出现大量D状态进程,说明它们在集体排队等磁盘。
一条命令就能列出所有D状态的进程:
bash复制ps -eo state,pid,comm | awk '$1 ~ /D/'
配合top里的wa列和iostat的await,基本能把“I/O高负载”这件事坐实。做了这一层确认之后,再上iotop去定位是哪个进程干的,排查效率会高很多,也不会在错误的路上浪费时间。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. iotop实战:从进程里把“磁盘杀手”揪出来
如果说iostat回答的是“磁盘有多忙”,那iotop回答的就是“谁让磁盘这么忙”。它是按进程维度实时展示I/O读写速率的利器,原理是读取/proc/<pid>/io的统计计数,再计算采样周期内的差值。
2.1 iotop的正确打开方式:参数组合与界面解读
Ubuntu 22.04上安装很简单:
bash复制apt install iotop
运行iotop需要root权限,因为要读取每个进程的/proc/<pid>/io文件。直接敲iotop进入的是交互式界面,类似top,会实时刷新每个进程的磁盘读写速率。但我实际工作中很少干瞪眼看交互界面,因为问题通常不是盯着能盯出来的,尤其当I/O高峰是间歇性的。
我更常用的组合是:
bash复制iotop -o -b -d 5 -k -P -t
逐个参数拆开说:
-o:only,只显示有I/O活动的进程,过滤掉那些读写了0字节的进程,让输出清爽很多。-b:batch,批量模式,不再需要交互,输出直接打到终端或文件。-d 5:每5秒采样一次。-k:以KB为单位显示,默认可能是字节,数字太大了看着很累。-P:只显示进程,不显示线程。线程会把输出刷得特别长,第一轮用-P把范围收敛到进程层面更高效。-t:在每行输出前加时间戳,方便和业务日志、系统日志做时间对齐。
跑一轮,同时把输出重定向到一个文件:
bash复制iotop -o -b -d 5 -k -P -t > /tmp/iotop_$(date +%F_%H%M%S).log 2>&1 &
让它在后台跑个30到60秒,等I/O高峰出现,再回来看日志。
2.2 间歇性I/O高峰的采样技巧
I/O问题最磨人的不是持续高负载,而是那种每过几分钟就抽风一次的间歇性高峰。这种情况下,如果只跑一次iotop,很可能正好赶在风平浪静的间隙,什么都抓不到。
我的做法是写个简单的循环,让它连续采一段时间,把每次采样的时间戳和I/O排行都留下来:
bash复制for i in $(seq 1 120); do
echo "===== $(date '+%F %T') =====" >> /tmp/iotop_sampling.log
iotop -o -b -d 1 -k -P -t -n 1 >> /tmp/iotop_sampling.log 2>&1
sleep 4
done
这个循环会持续120次,每次间隔4秒加1秒采样,相当于10分钟左右的观测窗口。配合-n 1参数让iotop只采样一次就退出,整个脚本在采样间隙不占额外I/O,不会干扰现场。
采集完成后,重点看时间戳附近出现了哪些平时不在的进程。我曾经在一次间歇性卡顿中,靠这个方案抓到了一个按小时启动的临时数据分析任务,它在启动瞬间会读取一个巨大的数据文件到内存计算,持续两三分钟后被系统OOM杀掉,然后又重启,如此反复,形成一个周而复始的I/O黑洞。
2.3 用pidstat做轻量替代与交叉验证
iotop依赖内核的/proc/<pid>/io,如果某些权限或环境因素导致它无法正常工作,我一般切换到一个同样能按进程统计I/O的工具:pidstat。它也属于sysstat包。
bash复制pidstat -d 1
这个命令会每秒输出一次每个进程的kB_rd/s、kB_wr/s和kB_ccwr/s(取消写操作)。它的好处是输出更稳定、格式更适合脚本处理,对系统资源的占用也更小。唯一的问题是它同样只能覆盖进程级别,遇到内核线程直接搞不定。
两个工具交叉验证的好处在于,能确认数据不是某个工具的统计口径导致的误判。有一次我看到iotop显示某个服务在大量写盘,但pidstat里它的写速率却低了一个数量级。后来发现是那个服务在频繁创建和删除临时文件,iotop统计的是文件系统层面的实际I/O,而pidstat统计的是块设备层面的落盘速率——两个工具的参考点不同,这个差异本身也成了线索,后来定位到是临时文件风暴问题。
3. systemd的双重角色:I/O统计审计与限流手术
找到可疑进程只是第一步,接下来需要回答两个问题:这个进程到底是不是服务的一部分?如果是,怎么在不杀进程的前提下先限制它对系统的影响?这些场景下systemd都能派上用场。Ubuntu 22.04使用systemd作为初始化系统,每个服务都被纳入独立的cgroup管理,天然具备按服务维度统计和限制I/O的能力。
3.1 systemd-cgtop与服务级I/O统计
先介绍一个平时存在感不强,但排查I/O问题时非常好用的命令:systemd-cgtop。它按cgroup维度展示资源占用情况,默认展示CPU和内存,加--io参数后会展示I/O相关数据。
bash复制systemd-cgtop --io
运行后能看到每个systemd服务对应的cgroup路径、每秒读写的速率、以及累计I/O量。相比iotop的进程视角,systemd-cgtop是服务视角,很适合在托管了大量服务的机器上快速判断是哪个“服务单元”在产生压力。
但这里有一个前提:服务单元的I/O数据统计依赖IOAccounting开关。如果某个服务没有开启这个选项,systemd-cgtop里它的I/O读数会始终为零。在Ubuntu 22.04默认cgroup v2环境下,我通常在怀疑某个服务之前,先顺手打开它的I/O记账,这不影响性能,但能把长期观测数据积累下来。
bash复制systemctl edit nginx.service
在打开的编辑窗口里写入:
ini复制[Service]
IOAccounting=yes
保存后重载并重启服务:
bash复制systemctl daemon-reload
systemctl restart nginx.service
之后再用systemctl status nginx.service就能看到该服务的I/O读写统计。这个做法适合对核心服务开启,长期观察后如果某个服务的I/O量出现异常增长,你会在它真正拖垮系统之前就发现苗头。
3.2 应急限流:systemd-run里的即时手术刀
当iotop已经抓到某个进程在疯狂读写磁盘,但你又不能立刻停掉它时——比如它正在执行一个重要的迁移任务,或者你还没搞清它的真实用途——临时限流就是最好的止血手段。
systemd-run可以把任意命令封装成一个临时作用域(scope)来运行,借助cgroup的I/O控制器直接给这个命令套上I/O上限。比较实用的两种限流方式分别是按权重分配和按带宽硬限制。
按权重分配适合“降低它对别人的影响”,把权重拉到很低的水平:
bash复制systemd-run --scope -p IOWeight=10 -p IOSchedulingClass=idle /path/to/command
按带宽硬限制适合“无论别人如何,你最多只能写这么多”:
bash复制systemd-run --scope -p IOReadBandwidthMax=/dev/sda 10M -p IOWriteBandwidthMax=/dev/sda 10M /path/to/command
IOWeight的取值范围是1到10000,默认值是100。设置为10相当于把该进程的I/O竞争权重压到默认值的十分之一。IOSchedulingClass=idle则把该进程的I/O调度类别降到idle级别,只在磁盘空闲时才能执行I/O,这是比权重更狠的压制。带宽限制需要指定设备路径,用lsblk确认实际设备名再填。
更关键的一点是:systemd-run --scope是临时性的,命令跑完,cgroup和相关限制自动销毁,不会污染系统里的服务定义。所以紧急情况下我敢大胆用它——就算判断错了,也不至于留下一个错误的服务配置。
3.3 长效治理:服务单元里的I/O配额与优先级
如果确认某个服务长期就是磁盘I/O大户,而且你有意压低它的I/O份额,那就应该把限制写进服务单元配置里,让它在服务启动时自动生效。
用systemctl edit给指定服务添加drop-in配置,例如给一个日志采集服务做限制:
bash复制systemctl edit filebeat.service
ini复制[Service]
IOAccounting=yes
IOWeight=20
IOReadBandwidthMax=/dev/sda 20M
IOWriteBandwidthMax=/dev/sda 20M
IOSchedulingClass=best-effort
IOSchedulingPriority=7
保存后重载服务:
bash复制systemctl daemon-reload
systemctl restart filebeat.service
这些参数的语义如下:
| 参数 | 作用 | 有效值 |
|---|---|---|
IOAccounting=yes |
开启该服务的I/O记账 | 布尔值 |
IOWeight= |
设置I/O权重,数值越小越容易被别的高权重服务抢占带宽 | 1-10000,默认100 |
IOReadBandwidthMax= |
设置读带宽硬上限 | 支持K/M/G后缀 |
IOWriteBandwidthMax= |
设置写带宽硬上限 | 支持K/M/G后缀 |
IOSchedulingClass= |
设置I/O调度类别 | none/realtime/best-effort/idle |
IOSchedulingPriority= |
设置同类调度下的优先级,数值越小优先级越高 | 0-7 |
验证配置是否生效,可以用:
bash复制systemctl show filebeat.service -p IOWeight,IOReadBandwidthMax,IOWriteBandwidthMax
返回的结果里能看到服务实际加载的参数值。如果发现配置没生效,多半是cgroup控制器或设备路径的问题,这个坑我在第5章会细说。
限制带宽和调整优先级这两件事要配合理解:带宽上限是硬约束,不管磁盘空不空闲,最多只能写到这个值;IOWeight是软分配,只在磁盘繁忙时起作用,让高权重服务优先获得I/O资源。现实中我也见过两个场景都适用的案例:一个前端服务和后端批处理任务共用一台机器,批处理任务设了低IOWeight之后,前端服务的I/O延迟从几百毫秒降到了几十毫秒,而批处理任务只是稍微变慢了一点,整体收益明显。
3.4 用journalctl把异常服务的日志时间线对齐
systemd还有一个在I/O排查中很有价值的能力:统一日志管理。当你在iotop的时间戳里看到某个进程在某个时间点开始疯狂写盘时,下一步就是去journalctl里翻那个时间点附近发生了什么。
bash复制journalctl --since "2024-12-01 14:20:00" --until "2024-12-01 14:35:00" -u app.service
比如我曾经遇到一个Java服务每隔一段时间就出现I/O尖峰,回头看journalctl对应的窗口,发现了大量“OutOfMemoryError”和“GC overhead limit exceeded”的日志,说明那个服务的内存配置早就失衡,Java进程在频繁触发Full GC,而GC过程伴随大量的内存换入换出和堆Dump写盘,把I/O带崩了。
或者反过来,如果journalctl在某个时间段里出现了某个服务“Failed to start”和“Started”反复交替的记录,那就要怀疑服务在崩溃重启循环中不断产生日志和临时文件,导致I/O持续高压。这种时间线对齐的排查方法是systemd生态独有的优势,一定要利用起来。
4. 根因排查清单与典型优化方向
iotop和systemd能帮你定位到“谁在制造I/O”,但很多情况下这还不够。你还需要往深挖一层:为什么这个进程会产生这么多I/O?这一章我整理了四个最常见的根因方向,每个方向都附带具体的检查命令和优化手段。
4.1 日志狂写:journald与logrotate的配额治理
日志是最常见的“隐形磁盘杀手”。默认的journald配置里,SystemMaxUse通常是磁盘总容量的10%,也就是说一台500G的服务器,journald日志理论上最多能占到50G。如果某个服务的日志级别被调到DEBUG,加上每秒钟几百个请求,生成日志的速度会非常可怕。
先检查当前journald日志的磁盘占用:
bash复制journalctl --disk-usage
如果看到几个G甚至几十个G,就需要限制一下。修改/etc/systemd/journald.conf:
ini复制[Journal]
SystemMaxUse=500M
SystemMaxFileSize=100M
MaxRetentionSec=7day
然后重启journald服务:
bash复制systemctl restart systemd-journald
应用日志方面,检查/var/log/下哪些日志文件特别大:
bash复制du -sh /var/log/* | sort -k1h | tail -20
针对异常增长的应用日志,配置logrotate,比如/etc/logrotate.d/app:
nginx复制/var/log/myapp/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
这个配置的含义是每天轮转一次,保留7份历史日志,启用压缩。copytruncate选项适合那些持续持有文件句柄的进程——先复制一份再清空原文件,避免因为日志被轮转导致进程写错位置。实际踩坑下来,很多服务在默认create轮转策略下会继续往旧文件句柄里写,磁盘空间反而没释放,copytruncate能有效规避这类问题。
4.2 内存不足引发的swap反复读写
如果在iotop里看不到特别突出的进程,但整个系统的I/O压力却居高不下,这时候就要高度怀疑swap问题的存在。swap反复读写会让整台机器陷入一种“所有进程都慢,但没有一个进程是明显元凶”的诡异状态。
先用free -h看内存和swap的使用情况,再用vmstat 1观察si和so两列。如果这两列持续有非零值,说明内存和磁盘之间在不停搬运数据。在这个场景下,真正的问题不是I/O本身,而是内存容量或内存配置已经无法支撑当前的工作负载。
降低swap对系统响应的影响,第一反应是把vm.swappiness调低:
bash复制sysctl vm.swappiness=10
永久生效需要写入配置文件:
bash复制echo "vm.swappiness=10" > /etc/sysctl.d/99-swappiness.conf
sysctl --system
swappiness默认是60,数值代表内核倾向于使用swap的程度。对大多数服务器场景,10左右是比较稳的设定——优先回收文件缓存而不是把匿名页换到磁盘去。这个调优不能解决内存不够的根源,但能明显缓解swap带来的I/O压力。如果确认是内存不足,该加内存加内存,该限制服务内存的用systemd的MemoryMax=参数做上限,双管齐下。
4.3 定时任务与批量作业的“撞车”问题
很多I/O高峰不是单个进程导致的,而是多个定时任务赶在同一时刻并发执行,形成叠加放大的效果。比如凌晨2点,一个MySQL全量备份在写盘,一个日志压缩任务在压缩归档,一个统计脚本在扫描全站文件,三路并发同时落到磁盘上,I/O瞬间被打爆。
这种问题排查需要把系统的定时任务全部列出来:
bash复制crontab -l
cat /etc/crontab
ls /etc/cron.d/
systemctl list-timers
systemctl list-timers尤其值得多看几眼,因为systemd定时器是现代化替代cron的方案,很多服务默认就挂着定时器。找到哪些任务集中在同一时段后,错峰调整它们的执行时间,或者通过TasksMax、IOWeight等systemd参数限制定时器服务的资源占用。调整的原则是把I/O密集的任务和在线业务的高峰时段错开,同时避免多个I/O密集任务在同一时刻启动。
4.4 文件系统层面容易被忽视的拖累点
进程排查完了,定时任务也错峰了,I/O压力还是高,那就需要往文件系统层面看。
一个大方向是检查文件系统挂载参数。很多Linux发行版默认开启了atime更新,意味着每次读文件都要写访问时间记录,在大量小文件读场景下会产生可观的额外写I/O。检查当前挂载参数:
bash复制mount | grep -E '^/dev/' | head -20
如果挂载参数里没有noatime,可以用remount在线调整:
bash复制mount -o remount,noatime /data
对长期生效,需要同步修改/etc/fstab里对应的挂载配置。
另一个容易被忽略的点是目录树过大导致的操作延迟。比如某个目录下有几十万个文件,ls和find这些操作本身会触发大量的元数据I/O,虽然每次吞吐量不大,但在高并发下会拉高await。用下面这个命令快速定位大目录:
bash复制du -x --max-depth=1 / | sort -k1h | tail -20
如果发现某个目录文件数量大到不合理,优先解决应用层面的文件堆积问题。之前遇到过一台机器上/tmp下残留了几十万个临时文件,导致每次系统扫描/tmp都卡半天,清理之后整个系统都变轻快了。
5. 这些坑,我不希望你再踩一遍
排查I/O问题有一些常见的“陷阱”,如果没遇到过,很容易在错误的路上浪费大量时间。这一章把我反复踩过的坑集中记录下来,希望能帮你绕开。
5.1 真凶可能是page cache回写,而不是某个进程
这是新手最容易产生误判的场景。一个进程在疯狂写数据,但内存还没有把脏页全部刷到磁盘,此时iotop里看到进程的写速率可能并不高。真正的高I/O发生在之后某个时刻,内核的flush线程开始把积累的脏页写入磁盘,此时iotop里的“凶手”变成了一个kworker线程。
我在一台机器上遇到过类似情况:一个应用短时间写了几十个GB的数据,当时iotop显示它的写速率并不夸张,但几个小时后系统开始出现I/O尖峰,iotop里看到的却是kworker/u64:0在持续写盘。
面对这种情况,先看/proc/meminfo里的Dirty和Writeback两栏:
bash复制grep -E '^(Dirty|Writeback):' /proc/meminfo
如果Dirty数值非常大,说明大量数据还在内存中等待被刷盘,I/O压力只是被延迟了。相关的内核参数是vm.dirty_ratio和vm.dirty_background_ratio,分别控制脏页占总内存的百分比达到多少时强制同步回写、以及达到多少时启动后台回写。对某些写压力大的服务器,适当调低vm.dirty_background_ratio能让回写过程更平滑,避免I/O在某个时刻突然集中爆发。
5.2 jbd2/kworker:看不到进程归属的内核I/O
就算你用iotop -a把累计I/O排行拉出来,也经常会看到jbd2/sda1-8这类内核线程的名字。jbd2是ext4文件系统日志模块的线程,它的I/O高意味着文件系统元数据操作频繁,比如大量创建、删除、重命名文件。
这种情况下iotop和systemd能提供的线索非常有限,因为它们都只能追踪到内核线程,无法反推是哪个用户态应用触发了这些元数据操作。我一般会用strace去跟踪可疑进程的系统调用,或者用auditd启用在特定目录下的文件系统操作审计,从应用行为层面找根因。
一个比较典型的案例是:某个服务在循环中反复执行“写临时文件、读临时文件、删除临时文件”的操作,每次操作都不大,但每秒几十上百次,jbd2的写量就被这些零碎的元数据刷新推得很高。这种情况下优化方向不是调I/O参数,而是改应用逻辑,把临时文件放到tmpfs上,或者干脆复用内存对象。
5.3 Docker/容器场景下的识别难度
服务器上跑了一堆Docker容器时,iotop显示的进程PID是宿主机视角的PID,和容器内部的PID完全对不上。你看到PID 2345在疯狂写盘,但不知道它是哪个容器里的进程。
可以把容器PID关联到容器ID和名称:
bash复制for cid in $(docker ps -q); do
pid=$(docker inspect -f '{{.State.Pid}}' "$cid")
name=$(docker inspect -f '{{.Name}}' "$cid")
echo "$name -> host PID $pid"
done
拿着iotop里的PID,在宿主机进程表里反查它属于哪个cgroup:
bash复制cat /proc/<PID>/cgroup
输出里能看到/system.slice/docker-<containerid>.scope这样的路径,容器ID到手后,docker ps --no-trunc | grep <containerid>就能定位到容器名。这种“从进程反查容器”的方式虽然绕,但很管用。
如果是Kubernetes环境,节点上还有kubelet管理的各种sandbox和container,排查方式类似,只是cgroup路径会更长。核心思路不变:通过/proc/<PID>/cgroup找到归属,再映射到具体的业务服务。
5.4 云盘外部因素:本地工具看不到的瓶颈
在云服务器上,磁盘设备底层是共享存储,I/O延迟和吞吐不止取决于你这一台实例,还受到宿主机其他邻居、存储集群健康状态的影响。如果你发现iostat里await很高,但本机的读写量并不大,而且iotop里也找不到异常进程,那问题很可能在本地工具看不到的层面。
这种情况只能从两个方向验证:一是看云平台的监控面板,确认是否是底层存储出现了性能波动;二是看是否有磁盘性能突发额度之类的概念被耗尽。有些云厂商的云盘,超出基准性能后会被限流,瞬时恢复但持续高负载时段就会跌到一个很低的速度。
排查的纪律是:先花半小时排除本机的进程、日志、swap、定时任务因素,再联系云厂商。没有完整数据支撑就去找厂商,大概率会被打回来,排查的效率也低。
5.5 systemd限流配置不生效的常见原因
通过systemd配置I/O限制,偶尔会遇到设置不生效的情况,先把下面几个检查项过一遍:
第一,确认系统使用的是否是cgroup v2。Ubuntu 22.04默认是cgroup v2,但如果是旧系统升级上来的,可能还挂在cgroup v1。用mount | grep cgroup看一下,默认挂载点是/sys/fs/cgroup,如果同时看到cgroup2,说明v2已经启用。I/O控制器的完整功能在v2下支持更好,v1下虽然也有blkio控制器,但语义和行为细节有差异。
第二,确认服务单元里是否开启了IOAccounting=yes。没有这个开关,I/O统计和带宽限制都不会生效。
第三,确认设备路径填写正确。用lsblk看设备名,云主机上设备名可能是vda,也可能是xvda,nvme0n1,填错了自然不会生效。
第四,确认服务重启后配置确实加载了。用systemctl show验证。
bash复制systemctl show nginx.service -p IOWeight,IOReadBandwidthMax,IOWriteBandwidthMax
如果输出里没有参数值或者值是空的,说明配置没有被正确解析,回看systemctl cat nginx.service确认drop-in文件确实被加载。
最后再提醒一句:限制I/O的目的是保护关键业务,不要一上线就给所有服务都加上低权重或者带宽限制。过度限流会导致业务程序变慢,甚至在极端情况下延长任务执行时间、增加I/O总量。我一般先只对确认是干扰源的服务做限制,观察几天,再逐步调整参数。
最后分享一个我自己的实操小习惯:遇到任何I/O异常,先花30秒把iostat -x 1、vmstat 1和/proc/pressure/io的现场记录存成一个文件,再开始动手排查。排查完之后回头对比这些基线数据,往往能发现一些一开始没留意的细节。这套IoT排查方法我反复验证下来,最大的价值不在于某个具体的命令,而在于把“量化确认I/O瓶颈 → 定位具体进程 → 用systemd控制影响 → 深挖根因”这个闭环走完整。下次再遇到服务器“半死不活”,你至少知道该从哪里下手。
