前言
服务器CPU不高、内存够用,但就是慢。top一看,%wa(IO wait)长期在50%以上,说明CPU在等磁盘IO。
磁盘IO慢的原因很多:调度器不合适、文件系统配置不当、硬件瓶颈、或者应用IO模式有问题。这篇文章从系统层面到应用层面,讲清楚怎么定位和优化。
一、IO性能指标:先看懂这些数据
1.1 基础指标
# 查看IO统计iostat -x 1# 输出示例Device r/s w/s rkB/s wkB/s await svctm %utilsda 50.00 30.00 2048.00 1536.00 12.50 8.00 64.00
关键指标:
await:平均等待时间(毫秒),包括队列等待+服务时间
判断标准:
await > svctm:说明队列等待时间长,IO压力大svctm > 20ms:硬件可能有问题(SSD通常<1ms)
1.2 IO wait的含义
# top或htop查看top# 看 %wa 这一列# 或者用vmstatvmstat 1# 看 wa 这一列
%wa表示CPU等待IO完成的时间占比。如果%wa高但%util不高,可能是:
- 应用在等IO,但IO设备本身不忙(可能是网络IO或其他设备)
1.3 查看进程IO
# iotop查看进程IO(需要root)iotop# 或者用pidstatpidstat -d 1# 输出示例PID kB_rd/s kB_wr/s kB_ccwr/s Command12345 1024.00 512.00 0.00 java
二、IO调度器:选择合适的策略
2.1 查看当前调度器
# 查看调度器cat /sys/block/sda/queue/scheduler# 输出示例noop [deadline] cfq# 方括号表示当前使用的调度器
2.2 常见调度器对比
| | |
|---|
noop | | |
deadline | | |
cfq | | |
mq-deadline | | |
2.3 切换调度器
# 临时切换echo deadline > /sys/block/sda/queue/scheduler# 永久切换(CentOS/RHEL)# 在 /etc/udev/rules.d/60-io-scheduler.rules 添加:ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/scheduler}="deadline"# 永久切换(Ubuntu/Debian)# 在 /etc/default/grub 修改:GRUB_CMDLINE_LINUX="elevator=deadline"# 然后 update-grub 和 reboot
2.4 调度器参数调优
deadline调度器参数:
# 查看参数cat /sys/block/sda/queue/iosched/read_expirecat /sys/block/sda/queue/iosched/write_expirecat /sys/block/sda/queue/iosched/fifo_batch# 调整参数(单位:毫秒)echo 500 > /sys/block/sda/queue/iosched/read_expire # 读请求截止时间echo 5000 > /sys/block/sda/queue/iosched/write_expire # 写请求截止时间
建议:
- 数据库:用
deadline,读截止时间500ms,写截止时间5000ms
三、文件系统优化
3.1 挂载选项优化
# 查看当前挂载选项mount | grep /dev/sda1# 优化后的挂载选项(ext4)mount -o noatime,nodiratime,data=writeback,barrier=0 /dev/sda1 /data# 或者写入 /etc/fstab/dev/sda1 /data ext4 noatime,nodiratime,data=writeback,barrier=0 0 2
常用选项:
data=writeback:延迟写回,提高性能(有数据丢失风险)data=ordered:先写数据再写元数据(平衡性能和安全性)barrier=0:关闭写屏障,提高性能(有数据丢失风险)
建议:
- 数据库数据目录:
noatime,nodiratime,data=ordered - 日志目录:
noatime,nodiratime,data=writeback
3.2 ext4文件系统优化
# 格式化时优化mkfs.ext4 -O ^has_journal -E lazy_itable_init=0,lazy_journal_init=0 /dev/sda1# 调整日志大小(默认128MB)tune2fs -J size=512 /dev/sda1# 调整保留空间(默认5%)tune2fs -m 1 /dev/sda1 # 保留1%
参数说明:
^has_journal:关闭日志(性能最高,但崩溃可能丢数据)lazy_itable_init=0:立即初始化inode表(格式化慢,但后续快)-J size=512:日志512MB(大日志减少日志切换)
3.3 XFS文件系统优化
# 格式化时优化mkfs.xfs -d su=256k,sw=2 -l size=512m /dev/sda1# 挂载选项mount -o noatime,nodiratime,largeio,inode64 /dev/sda1 /data
参数说明:
su=256k,sw=2:条带大小256KB,条带数2(RAID优化)
3.4 文件系统选择建议
四、应用层IO优化
4.1 减少IO次数
问题:频繁小文件读写
# 不好的做法:每次写一行for i in {1..10000}; doecho"line $i" >> file.txtdone# 好的做法:批量写入{ for i in {1..10000}; doecho"line $i"; done } > file.txt
问题:随机IO多
# 如果可能,改为顺序IO# 比如:先收集数据,再批量写入# 或者:用内存缓存,定期刷盘
4.2 使用异步IO
同步IO:read()/write()阻塞直到完成
异步IO:
- Linux 5.1+:
io_uring(高性能异步IO) - 传统:
aio_read()/aio_write()(但文件IO支持不好)
io_uring示例(需要编程):
// 伪代码示例structio_uringring;io_uring_queue_init(32, &ring, 0);// 提交读请求structio_uring_sqe *sqe = io_uring_get_sqe(&ring);io_uring_prep_read(sqe, fd, buf, size, offset);io_uring_submit(&ring);// 等待完成structio_uring_cqe *cqe;io_uring_wait_cqe(&ring, &cqe);
4.3 数据库IO优化
MySQL:
# my.cnfinnodb_flush_log_at_trx_commit = 2 # 每秒刷日志(性能更好,但可能丢1秒数据)innodb_flush_method = O_DIRECT # 绕过OS缓存innodb_io_capacity = 2000 # SSD设置为2000-4000innodb_read_io_threads = 4innodb_write_io_threads = 4
PostgreSQL:
# postgresql.confshared_buffers = 4GBeffective_cache_size = 12GBrandom_page_cost = 1.1 # SSD设置为1.1-1.5
4.4 日志IO优化
问题:日志频繁刷盘影响性能
方案:
# 示例:Java Logback异步日志<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"> <queueSize>512</queueSize> <appender-ref ref="FILE" /></appender>
五、硬件层面优化
5.1 RAID配置
建议:
5.2 SSD优化
# 查看SSD信息smartctl -a /dev/sda# 启用TRIM(自动回收)# ext4: mount时加 discard 选项# 或者定期执行: fstrim /data# 调整调度器echo noop > /sys/block/sda/queue/scheduler# 或echo mq-deadline > /sys/block/sda/queue/scheduler
5.3 多路径IO(MPIO)
如果使用SAN存储,配置多路径提高性能和可用性:
# 安装multipathyum install device-mapper-multipath# 配置mpathconf --enable --with_multipathd y# 查看多路径multipath -ll
六、实战案例:定位IO瓶颈
6.1 案例1:数据库慢
现象:数据库查询慢,%wa高
排查步骤:
# 1. 看IO统计iostat -x 1# 发现 %util 90%,await 50ms# 2. 看哪个进程在IOiotop# 发现MySQL进程IO高# 3. 看MySQL慢查询日志# 发现大量随机读# 4. 优化方案# - 调整innodb_buffer_pool_size(增加缓存)# - 优化查询(加索引、避免全表扫描)# - 调整调度器为deadline
6.2 案例2:日志写入慢
现象:应用日志写入慢,影响性能
排查步骤:
# 1. 看IO统计iostat -x 1# 发现写IO高,await 30ms# 2. 看进程IOpidstat -d 1# 发现Java进程写IO高# 3. 优化方案# - 日志改为异步写入# - 日志目录用data=writeback挂载# - 日志轮转,限制文件大小
6.3 案例3:文件系统慢
现象:文件操作慢,但硬件性能正常
排查步骤:
# 1. 看IO统计iostat -x 1# 发现IOPS不高,但await高# 2. 看调度器cat /sys/block/sda/queue/scheduler# 发现用的是cfq,不适合当前场景# 3. 优化方案# - 切换调度器为deadline或noop# - 调整挂载选项(noatime等)
七、跨网络IO测试与监控
7.1 场景:分布式存储性能测试
如果需要在多台服务器上测试IO性能,逐个SSH登录很麻烦。
解决方案:
- 组网工具:WireGuard、ZeroTier、星空组网等,把机器组成虚拟局域网
用组网工具后,不管机器实际在哪,都可以用虚拟内网IP直接访问。然后用Ansible批量执行IO测试:
# Ansible批量执行fio测试ansible all -m shell -a "fio --name=test --filename=/tmp/test --size=1G --rw=randwrite --ioengine=libaio --bs=4k --iodepth=32 --runtime=60 --time_based"# 或者用脚本批量收集IO统计for host in 10.0.0.{1..10}; do ssh $host"iostat -x 1 10" > iostat_${host}.logdone
7.2 场景:跨网络存储访问
如果应用需要访问远程存储(NFS、CIFS等),网络延迟会影响IO性能。
优化方案:
# 示例:NFS挂载优化mount -o rsize=1048576,wsize=1048576,hard,intr,timeo=600 /nfs/server /mnt/nfs# 参数说明# rsize/wsize: 读写块大小,默认32KB,可以调大到1MB# hard: 服务器不可用时挂起(不要用soft,可能丢数据)# timeo: 超时时间(0.1秒单位)
八、监控与告警
8.1 关键指标监控
# 脚本:监控IO指标#!/bin/bashwhiletrue; do iostat -x 1 1 | grep -E "Device|sda" | awk '{print $10}' | tail -1 sleep 60done# 如果 %util > 80%,告警
8.2 Prometheus监控
# node_exporter 已经包含IO指标# 查询:node_disk_io_time_seconds_total# 查询:node_disk_read_time_seconds_total# 查询:node_disk_write_time_seconds_total
8.3 告警规则
# Prometheus告警规则groups:-name:disk_iorules:-alert:HighDiskIOUtilexpr:rate(node_disk_io_time_seconds_total[5m])>0.8for:5mannotations:summary:"磁盘IO利用率过高"-alert:HighDiskIOWaitexpr:rate(node_disk_io_time_weighted_seconds_total[5m])>0.5for:5mannotations:summary:"磁盘IO等待时间过长"
九、常见问题与注意事项
9.1 调度器切换不生效
# 检查是否真的切换了cat /sys/block/sda/queue/scheduler# 如果还是旧的,可能是:# 1. 设备被其他进程占用# 2. 需要重启# 3. 内核不支持该调度器
9.2 挂载选项不生效
# 检查当前挂载选项mount | grep /dev/sda1# 如果选项不对,可能是:# 1. /etc/fstab配置错误# 2. 需要重新挂载:mount -o remount /data
9.3 性能测试不准确
# 用fio做标准测试fio --name=test \ --filename=/tmp/test \ --size=1G \ --rw=randwrite \ --ioengine=libaio \ --bs=4k \ --iodepth=32 \ --runtime=60 \ --time_based \ --direct=1# 参数说明# --direct=1: 绕过OS缓存,测试真实磁盘性能# --iodepth: IO队列深度,模拟并发# --bs: 块大小,模拟实际IO模式
9.4 生产环境调优风险
注意事项:
barrier=0或data=writeback:有数据丢失风险,需要UPS
建议流程:
总结
核心思路:
注意事项: