生产级 Linux 磁盘 IO 调优
磁盘 IO 性能是 Linux 服务器最常见的瓶颈之一。iowait 高、响应延迟增、吞吐下降时,掌握从原理到工具到优化的完整方法论至关重要。
一、IO 性能三要素
| 指标 |
含义 |
典型值 |
| IOPS |
每秒 IO 操作次数 |
HDD ~150, SSD ~50k+, NVMe ~1M |
| 吞吐量 |
每秒传输数据量 |
HDD ~200MB/s, SATA SSD ~500MB/s, NVMe ~3GB/s+ |
| 延迟 |
单个 IO 请求完成时间 |
HDD 5-15ms, SSD 0.1-0.5ms, NVMe 0.02-0.2ms |
三者关系: IOPS × 平均 IO 大小 = 吞吐量。
二、存储介质与调度算法
存储类型选择
| 场景 |
推荐 |
| 数据库(随机 IO 为主) |
SSD 或 NVMe,高 IOPS 优先 |
| 文件存储(顺序 IO 为主) |
大容量 HDD 或 SATA SSD,吞吐优先 |
| 日志收集(顺序写为主) |
大容量 HDD 或 SATA SSD |
| 虚拟化/容器存储 |
至少 SSD,建议 NVMe |
IO 调度算法
| 调度器 |
适合 |
说明 |
| NOOP |
SSD / NVMe |
最简单的 FIFO,适合自带调度的高性能存储 |
| Deadline |
HDD / 数据库 |
优先处理即将超时的请求,减少延迟抖动 |
| mq-deadline |
多队列 NVMe |
专为多核多队列设计,现代默认 |
| CFQ |
HDD(公平) |
老版默认,SSD 上反而降低性能 |
| BFQ |
桌面/交互式 |
服务器一般不推荐 |
| KYBER |
高速存储 |
延迟感知调度 |
# 查看当前调度器
cat /sys/block/sda/queue/scheduler
# 临时修改(重启失效)
echo mq-deadline > /sys/block/sda/queue/scheduler
# 永久修改(udev 规则)
cat > /etc/udev/rules.d/60-io-scheduler.rules << 'EOF'
# SSD → noop
ACTION=="add|change", SUBSYSTEM=="block", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="noop"
# HDD → deadline
ACTION=="add|change", SUBSYSTEM=="block", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="deadline"
EOF
udevadm control --reload-rules
三、文件系统选型与挂载选项
| 文件系统 |
大文件 |
小文件 |
多线程 IO |
最大单文件 |
| XFS |
好 |
较好 |
很好 |
8EB |
| EXT4 |
好 |
一般 |
一般 |
16TB |
挂载选项
| 选项 |
作用 |
性能 |
安全 |
noatime |
不更新访问时间 |
✅ 减少元数据写 |
轻微 |
nodiratime |
不更新目录访问时间 |
✅ |
轻微 |
nobarrier |
关闭写屏障 |
✅ 提升写性能 |
❌ 断电可能丢数据 |
data=writeback |
EXT4 写回模式 |
✅ 最高写性能 |
❌ 可能丢数据 |
data=ordered |
EXT4 有序模式(默认) |
折中 |
折中 |
# 高性能场景(可接受数据风险)
mount -o noatime,nodiratime,nobarrier,data=writeback /dev/sdb1 /data
# 标准安全场景(推荐)
mount -o noatime,nodiratime /dev/sdb1 /data
四、性能评估工具
| 工具 |
用途 |
关键指标 |
| iostat -xcz 1 |
IO 统计 |
%util / await / avgqu-sz / avgrq-sz / r/s / w/s |
| iotop -b -o |
按进程查看 IO |
DISK READ/WRITE / IO% |
| pidstat -d 1 |
进程级 IO 统计 |
kB_rd/s / kB_wr/s / iodelay |
| top / atop |
快速判断 |
wa (iowait) 列 |
| fio |
基准测试 |
IOPS / 吞吐量 / 延迟分布 |
| hdparm -Tt |
磁盘缓存/读取速度 |
缓存读 / 磁盘读 |
iostat 核心指标解读
| 指标 |
含义 |
异常阈值 |
%util |
设备忙碌时间占比 |
> 80% 饱和,> 95% 严重 |
await |
平均 IO 响应时间 |
HDD > 50ms, SSD > 10ms, NVMe > 2ms |
avgqu-sz |
平均队列深度 |
> 4 较高,> 8 严重过载 |
avgrq-sz |
平均 IO 大小 |
< 16 sector (8KB) = 随机小 IO |
r/s / w/s |
每秒读/写请求数 |
结合 IOPS 上限判断 |
fio 基准测试
# 随机读(模拟数据库)
fio --name=randread --ioengine=libaio --iodepth=4 --rw=randread --bs=4k \
--size=1G --numjobs=4 --runtime=60 --time_based=1 --filename=/data/fio_test
# 顺序写(模拟大文件)
fio --name=seqwrite --ioengine=libaio --iodepth=16 --rw=write --bs=128k \
--size=2G --runtime=60 --time_based=1 --filename=/data/fio_test
五、瓶颈识别模式
| 模式 |
特征 |
根因 |
| 设备饱和 |
%util > 90%, await 高 |
设备能力不足 |
| 队列堆积 |
avgqu-sz > 8, await 高但 %util 不高 |
应用并发 IO 过多 |
| 元数据瓶颈 |
%util 不高但延迟高, avgrq-sz 很小 |
文件系统元数据操作频繁, 小文件读写 |
| 延迟抖动 |
await 时高时低, %util 波动大 |
存储后端不稳定, 多存储竞争 |
六、IO 优化实战
6.1 内核参数(脏页回写)
# /etc/sysctl.conf
# 脏页参数优化(高 IO 压力场景)
vm.dirty_ratio = 40 # 触发强制回写的内存比例(默认20)
vm.dirty_background_ratio = 10 # 后台回写的内存比例(默认10)
vm.dirty_expire_centisecs = 3000 # 脏页过期时间(30秒)
vm.dirty_writeback_centisecs = 500 # 回写间隔(5秒)
6.2 预读与队列深度
# 预读值
cat /sys/block/sda/queue/read_ahead_kb
# 数据库随机读场景 → 128-512KB(减小,不需要大预读)
# 大文件顺序读 → 1-4MB(增大)
echo 4096 > /sys/block/sda/queue/read_ahead_kb
# IO 队列深度
cat /sys/block/sda/queue/nr_requests
echo 256 > /sys/block/sda/queue/nr_requests
6.3 应用层优化
- 批量操作: 合并多次小 IO 为一次大 IO
- 异步 IO: 非关键 IO 异步处理,不阻塞主流程
- 缓存: 使用内存缓存减少磁盘 IO
- 预取: 对可预测的读操作提前预取
6.4 MySQL 数据库 IO 优化
[mysqld]
# Buffer Pool(设为可用内存 60-80%)
innodb_buffer_pool_size = 12G
innodb_buffer_pool_instances = 4
# IO 相关
innodb_flush_method = O_DIRECT # 绕过页面缓存,直接 IO
innodb_io_capacity = 2000 # SSD 建议 2000-10000
innodb_io_capacity_max = 4000
innodb_flush_log_at_trx_commit = 1 # 1=最安全, 2=折中, 0=最快(最多丢1秒数据)
# Redo log 优化
innodb_log_file_size = 1G
innodb_log_files_in_group = 3
innodb_log_buffer_size = 64M
6.5 LVM SSD 缓存加速 HDD
# 使用 SSD 加速 HDD 数据盘
lvcreate --type cache-pool -l 100%FREE -n cache_pool vg_data /dev/sda1
lvconvert --cache --cachemode writethrough vg_data/lv_data --cachepool vg_data/cache_pool
七、监控与告警
关键指标
| 指标 |
告警阈值 |
严重告警 |
| 磁盘利用率 |
%util > 80% 持续 5 分钟 |
> 95% 持续 1 分钟 |
| IO 响应时间 |
HDD > 30ms / SSD > 5ms |
HDD > 100ms / SSD > 10ms |
| 队列深度 |
avgqu-sz > 4 |
> 8 |
| 磁盘空间 |
使用率 > 80% |
> 95% |
| inode 使用率 |
> 80% |
> 95% |
Prometheus + node_exporter
# 磁盘利用率
rate(node_disk_io_time_seconds_total[5m]) * 100
# IO 延迟(读 await)
rate(node_disk_read_time_seconds_total[5m]) / rate(node_disk_reads_completed_total[5m]) * 1000
# 吞吐量
rate(node_disk_read_bytes_total[5m]) / 1024 / 1024
八、案例复盘
案例 1:MySQL 响应延迟(促销期 iowait 45%)
根因: innodb_flush_log_at_trx_commit=1 导致每次事务提交刷日志,促销事务量激增。
优化:
- 折中:改为
innodb_flush_log_at_trx_commit = 2(每秒刷一次,最多丢 1 秒数据)
- 增大 redo log:
innodb_log_file_size = 4G, innodb_log_files_in_group = 4
- 合并小事务、使用批量插入
案例 2:日志服务器写入阻塞
根因: 日志轮转未开启压缩,大量日志顺序写满机械硬盘。
优化: 开启 compress + delaycompress;将日志目录迁移至 SSD;使用异步写入。
案例 3:5000 万小文件 inode 耗尽
根因: EXT4 inode 耗尽,单目录 500 万文件,元数据操作成为瓶颈。
优化: 改用 XFS(inode 动态分配);拆分为多级哈希目录结构;或使用对象存储。
九、作业检查清单
新服务器部署:
- [ ] 根据 IO 特征选择存储类型
- [ ] 合理分区(数据/日志/临时文件分开)
- [ ] SSH → noop, HDD → deadline
- [ ] 挂载选项:
noatime,nodiratime
- [ ] 内核脏页参数按业务调整
- [ ] 部署监控 + 配置告警阈值
日常巡检:
- [ ]
df -h 空间 < 80%, df -i inode 充足
- [ ]
iostat -xcz 1 %util < 80%, await 正常
- [ ]
pidstat -d 1 检查异常 IO 进程
- [ ]
dmesg | grep -i error 检查硬件错误
关联页面
关联页面