性能之巅第5章:文件系统与磁盘I/O

📑 目录

文件系统和磁盘是最容易被"混为一谈"的两个层次。开发说"磁盘慢",可能实际是文件系统配置问题;运维说"文件系统满了",可能实际是inode耗尽。理解I/O栈的每一层,才能找准真正的瓶颈。


第5章 文件系统与磁盘:穿透I/O栈的迷雾

5.1 故事:那个"磁盘满了"的周三

周三上午,某视频平台的转码服务报警:"磁盘使用率98%"

运维小张慌了,开始清理日志:

find /var/log -name "*.log" -mtime +7 -delete
df -h

使用率降到了85%,但服务还是报错:"No space left on device"

"明明还有15%空间啊?"小张困惑。

他检查了inode:

df -i
# Filesystem     Inodes   IUsed   IFree IUse% Mounted on
# /dev/sda1      655360  655360       0  100% /

inode 100%了。

继续查:

# 找小文件最多的目录
find /data -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head
#  123456 /data/thumbnails

ls /data/thumbnails | wc -l
# 6000000

原来转码服务生成的缩略图是按毫秒时间戳命名的,每个视频几千张。600万个文件,每个文件平均2KB,inode用光了,磁盘空间还剩15%。

根因:不是磁盘空间问题,是文件系统设计问题——小文件过多耗尽inode。

修复

  1. 缩略图按视频ID分目录存储(每目录不超过1万文件)
  2. 定期合并小文件到对象存储(S3/OSS)
  3. 文件系统从ext4换到XFS(动态inode分配)

5.2 文件系统类型对比

graph TD
    A[文件系统选择] --> B[ext4]
    A --> C[XFS]
    A --> D[ZFS]
    A --> E[Btrfs]
    
    B --> B1[通用,成熟]
    B --> B2[固定inode]
    B --> B3[小文件多易出问题]
    
    C --> C1[大文件,数据库]
    C --> C2[动态inode]
    C --> C3[延迟分配优化]
    
    D --> D1[企业级,数据完整性]
    D --> D2[写时复制]
    D --> D3[压缩/快照]
    
    E --> E1[开发测试]
    E --> E2[类似ZFS]
    E --> E3[成熟度和ZFS

    style B fill:#e3f2fd
    style C fill:#e8f5e9
    style D fill:#fff3e0
特性ext4XFSZFSBtrfs
最大文件16TB8EB16EB16EB
最大卷1EB8EB256ZB16EB
inode固定分配动态分配动态动态
数据校验
压缩
快照
写时复制
适用场景通用大文件/DB数据安全开发测试

5.3 I/O栈全景

graph TD
    A[应用程序
read/write/mmap] --> B[VFS
虚拟文件系统] B --> C[ext4/XFS/ZFS] C --> D[日志层
JBD2] D --> E[页缓存
Page Cache] E --> F{Direct I/O?} F -->|否| G[脏页回写
writeback] F -->|是| H[绕过缓存] G --> I[块层
Block Layer] H --> I I --> J[I/O调度器] J --> K[设备驱动] K --> L[硬件队列
NVMe/SATA] L --> M[磁盘/SSD] style E fill:#e8f5e9 style I fill:#fff3e0 style M fill:#ffebee

关键层次

层次功能性能调优点
VFS统一接口,抽象具体文件系统dentry缓存
文件系统文件组织、元数据管理挂载参数
页缓存减少磁盘I/O缓存命中率
块层I/O请求合并、排序调度器选择
设备驱动硬件交互队列深度
硬件物理I/O执行SSD/HDD/NVMe

5.4 I/O调度器

调度器对比

graph LR
    subgraph "CFQ 完全公平队列"
        A1[按进程分组]
        A2[时间片轮转]
        A3[适合桌面]
    end
    
    subgraph "Deadline"
        B1[读写分离队列]
        B2[防止饥饿]
        B3[适合数据库]
    end
    
    subgraph "NOOP"
        C1[简单FIFO]
        C2[无排序]
        C3[适合SSD]
    end
    
    subgraph "BFQ"
        D1[预算机制]
        D2[带宽保证]
        D3[适合交互式]
    end
调度器算法适用场景
CFQ按进程分组,时间片轮转桌面系统(已过时)
Deadline读写分离,防止饥饿数据库、高IOPS
NOOP简单FIFOSSD、NVMe(推荐)
BFQ预算机制,带宽保证交互式、多媒体
Kyber双队列,快速设备NVMe(新选择)
# 查看当前调度器
cat /sys/block/sda/queue/scheduler
# [mq-deadline] kyber none

# 对于NVMe,通常用none(即NOOP)
cat /sys/block/nvme0n1/queue/scheduler
# none [mq-deadline] kyber

# 修改调度器
echo none > /sys/block/nvme0n1/queue/scheduler

# 永久生效(grub)
GRUB_CMDLINE_LINUX_DEFAULT="elevator=none"

5.5 磁盘性能分析

核心指标

# iostat输出解读
iostat -xz 1
Device      r/s    w/s    rkB/s    wkB/s   rrqm/s  wrqm/s  %rrqm  %wrqm r_await w_await aqu-sz  %util
nvme0n1   1000   500   4000     2000     200      100      20     20     0.50    0.30   0.75   45.0

# r/s, w/s:每秒读/写I/O数(IOPS)
# rkB/s, wkB/s:每秒读/写吞吐量
# r_await, w_await:每次读/写的平均等待时间(ms)
# aqu-sz:平均队列长度
# %util:设备忙碌时间百分比

关键指标解读

指标健康范围超标含义
%util<60%接近100%表示饱和
awaitSSD<1ms, HDD<10ms越高说明排队越久
aqu-sz接近1持续>1说明有排队

SSD vs HDD vs NVMe

graph TD
    subgraph HDD
        A1[旋转延迟 5-10ms]
        A2[寻道延迟 2-10ms]
        A3[IOPS 100-200]
        A4[吞吐 100-200MB/s]
    end
    
    subgraph SSD
        B1[无机械延迟]
        B2[FTL映射]
        B3[IOPS 10K-100K]
        B4[吞吐 500MB-3GB/s]
        B5[写入放大 WA]
    end
    
    subgraph NVMe
        C1[PCIe直连]
        C2[多队列 64K]
        C3[IOPS 500K+]
        C4[吞吐 3-7GB/s]
        C5[延迟 10μs]
    end
    
    style A1 fill:#ffebee
    style C1 fill:#e8f5e9

5.6 fio:磁盘性能测试标准工具

# ===== 随机读IOPS测试(NVMe) =====
fio --name=randread --directory=/mnt/nvme \
    --rw=randread --bs=4k --size=4G \
    --numjobs=4 --iodepth=32 --runtime=60 \
    --direct=1 --group_reporting

# 输出:
# randread: (groupid=0, jobs=4)
#    read: IOPS=125k, BW=488MiB/s

# ===== 混合读写(模拟数据库) =====
fio --name=mix --directory=/mnt/nvme \
    --rw=randrw --rwmixread=70 --bs=8k --size=4G \
    --numjobs=8 --iodepth=64 --runtime=120 \
    --direct=1

# ===== 顺序写(模拟日志) =====
fio --name=seqwrite --directory=/mnt/nvme \
    --rw=write --bs=1M --size=10G \
    --numjobs=1 --direct=1

5.7 挂载参数调优

# 数据库专用挂载(ext4)
mount -t ext4 -o noatime,nodiratime,nobarrier,data=writeback /dev/sdb1 /data

# 参数解释:
# noatime:不更新访问时间,减少写操作
# nodiratime:不更新目录访问时间
# nobarrier:禁用写屏障(SSD安全,HDD有风险)
# data=writeback:写回模式,最快但crash可能丢数据

# 对于XFS(推荐数据库)
mount -t xfs -o noatime,nobarrier,largeio,inode64 /dev/sdb1 /data

# 对于ZFS
zfs set compression=lz4 tank/data
zfs set recordsize=128K tank/data  # 数据库用8K或16K

5.8 本章总结

graph TD
    A[磁盘I/O问题] --> B{看iostat}
    B -->|util高| C[设备饱和]
    B -->|await高| D[排队或设备慢]
    B -->|aqu-sz高| E[请求堆积]
    
    C --> F[换更快设备/NVMe]
    D --> G[调调度器/扩容]
    E --> H[减并发/加队列深度]
    
    A --> I{文件系统}
    I -->|inode满| J[换XFS/ZFS]
    I -->|元数据慢| K[优化挂载参数]
    I -->|缓存miss| L[加大内存/Page Cache]
    
    style F fill:#e8f5e9
    style G fill:#e8f5e9
    style H fill:#e8f5e9

核心要点

  1. inode和磁盘空间是两个独立指标——小文件场景关注inode
  2. I/O调度器对SSD用none/NOOP,对HDD用deadline
  3. iostat的await比util更能反映真实延迟
  4. 挂载参数能显著提升性能——noatime和barrier选择需谨慎
  5. fio是测试磁盘性能的金标准——学会用它建立基线

"磁盘是系统中最慢的组件,但也是最容易被错误诊断的瓶颈。"