磁盘I/O是Linux服务器性能瓶颈中最常见的环节。数据库服务、文件服务器、日志采集系统在高负载场景下频繁出现I/O wait过高、响应延迟陡增的问题。通过iostat、blktrace等工具对磁盘I/O进行深度诊断,配合I/O调度器和文件系统参数调优,可以有效消除I/O瓶颈。本文以实际案例贯穿,给出完整的诊断和优化方案。
iostat工具输出字段深度解读
iostat是sysstat工具包中的核心I/O监控工具,通过读取/proc/diskstats获取磁盘统计信息。关键命令和参数:
# 每秒输出一次,显示扩展统计信息
iostat -dxm 1
# 输出示例
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %util await r_await w_await svctm
sda 120.50 45.20 3072.0 1152.0 8.30 2.10 78.65 4.72 3.15 8.91 0.58
各字段含义及诊断标准:
%util:磁盘利用率,表示磁盘在采样周期内有多少时间在处理I/O请求。超过80%表示磁盘接近饱和。但需要注意,对于SSD和NVMe等支持并发请求的设备,%util可能达到100%而设备并未真正饱和,此时应关注队列深度。
await:I/O请求的平均等待时间(毫秒),包含队列等待时间和服务时间。机械硬盘正常值在5-15ms,SSD应在1-3ms,NVMe应在0.5-1ms。await异常升高通常意味着磁盘负载过重或存在热点。
r_await与w_await:读请求和写请求的独立等待时间。两者差异过大时,说明读写在互相干扰,可能需要调整调度策略或分离读写 workload。
svctm:平均服务时间。该字段在新版sysstat中已标记为deprecated,因为现代存储设备的并发特性使其不再准确,建议通过await和队列长度综合判断。
aqu-sz:平均队列长度。持续大于1表示请求在排队等待,磁盘处理能力不足。
I/O调度器选型与内核配置
I/O调度器决定内核如何排序和合并磁盘请求。不同调度器适用于不同场景:
# 查看当前调度器
cat /sys/block/sda/queue/scheduler
# 输出: noop deadline [mq-deadline] kyber bfq
# 临时切换调度器
echo mq-deadline > /sys/block/sda/queue/scheduler
mq-deadline:多队列版deadline调度器,对每个请求设置截止时间,防止饥饿。适合SSD和通用服务器场景,也是CentOS/RHEL默认选择。
bfq:Budget Fair Queuing,按进程分配I/O带宽,适合桌面系统和交互式应用。在服务器高并发场景下,bfq的公平调度反而增加延迟,不建议使用。
none(noop):不做任何排序,直接将请求传递给设备。适用于NVMe等自带硬件队列管理的设备,减少软件层开销。
调度器选型建议:NVMe设备用none,SSD用mq-deadline,机械硬盘用mq-deadline或bfq(交互式场景)。
队列深度调优:
# 查看当前队列深度
cat /sys/block/sda/queue/nr_requests
# 默认通常为128
# 对于高并发数据库场景,适当增加队列深度
echo 256 > /sys/block/sda/queue/nr_requests
# 设置预读大小,对于顺序读密集型场景有显著效果
blockdev --setra 4096 /dev/sda
# 4096个sector = 2MB预读
文件系统挂载参数与I/O优化
ext4和xfs是最常用的Linux文件系统。挂载参数的合理配置对I/O性能有直接影响:
# /etc/fstab 推荐配置示例
UUID=xxx /data xfs defaults,noatime,nodiratime,lazytime 0 0
# ext4 额外参数
UUID=yyy /var/lib/mysql ext4 defaults,noatime,data=writeback,barrier=0 0 0
noatime/nodiratime:禁止更新文件访问时间。每次读文件默认会更新atime属性,产生额外写I/O,对读密集型场景影响明显。
data=writeback:ext4日志模式,只记录元数据变更,不记录数据变更。性能最高但崩溃时可能丢失最近写入的数据。仅适用于有应用层保证数据一致性的场景,如MySQL的InnoDB引擎(有自己的redo log)。
barrier=0:禁用写屏障。写屏障保证磁盘缓存中的数据按序刷盘,禁用后性能提升但断电时存在数据损坏风险。仅建议在UPS不间断电源环境下使用。
blktrace工具追踪I/O请求路径
当iostat显示磁盘负载高但无法定位具体来源时,blktrace可以追踪单个I/O请求的完整生命周期:
# 追踪sda设备的I/O事件,持续10秒
blktrace -d /dev/sda -o - | blkparse -i - > io_trace.txt &
sleep 10
kill %1
# 分析结果
# D -- I/O请求提交到设备驱动
# C -- I/O请求完成
# Q -- 请求进入队列
# G -- 请求与已有请求合并
# I -- 请求下发到设备
# 统计I/O大小分布
blkparse -i sda -b | awk '{print $NF}' | sort -n | uniq -c
biolatency是BCC工具包中的eBPF程序,可以统计I/O延迟分布直方图,比iostat的汇总平均值更有价值:
# 追踪I/O延迟分布
/usr/share/bcc/tools/biolatency -d sda
# 输出示例
usecs count distribution
0 -> 1 : 0 | |
1 -> 2 : 15 | |
2 -> 4 : 287 |********** |
4 -> 8 : 1024 |************************************ |
8 -> 16 : 1567 |************************************************|
16 -> 32 : 892 |***************************** |
通过直方图可以看到延迟分布。如果存在长尾(如少量请求延迟超过100ms),可能是磁盘队列满或存在坏扇区。
实战案例:MySQL服务器I/O瓶颈排查
某MySQL服务器在高峰期出现查询延迟从5ms上升到200ms的问题。诊断过程:
第一步,iostat确认磁盘负载:
iostat -dxm 1
# sda %util: 98.7% await: 45.3ms w_await: 52.1ms aqu-sz: 8.2
%util接近100%,await远超SSD正常范围,队列长度8.2说明请求大量排队。
第二步,通过iotop定位I/O来源:
iotop -oP
# 发现mysqld进程的IO速率为 385 MB/s,主要写入/ib_logfile和/tmp
InnoDB redo log写入是主要I/O来源。检查MySQL配置:
show variables like 'innodb_flush_log_at_trx_commit';
show variables like 'innodb_io_capacity';
show variables like 'sync_binlog';
show variables like 'innodb_buffer_pool_size';
发现innodb_flush_log_at_trx_commit=1(每次事务提交都刷盘),innodb_io_capacity=200(SSD应设为2000+)。优化方案:
# MySQL配置优化
innodb_io_capacity = 2000 # SSD增大IO能力上限
innodb_io_capacity_max = 4000 # 最大IO能力
innodb_flush_neighbors = 0 # SSD不需要刷邻居页
innodb_buffer_pool_size = 32G # 增大缓冲池减少磁盘读
innodb_log_file_size = 2G # 增大redo log减少checkpoint频率
# 如果应用层可容忍异常断电丢失1秒数据
innodb_flush_log_at_trx_commit = 2 # 每秒刷一次,而非每次事务
优化后iostat显示%util降至65%,await降至2.1ms,查询延迟恢复到5ms以内。
磁盘I/O调优需要硬件特性、操作系统参数和应用层配置三方面协同。盲目调内核参数而不分析I/O来源,往往收效甚微。建议先用iostat确认瓶颈位置,再用blktrace/biolatency定位具体磁盘操作,最后从操作系统和应用层面双管齐下才能取得理想效果。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/linux-ci-pan-io-xing-neng-diao-you-shi-zhan-iostat-shen-du/