性能之巅第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是测试磁盘性能的金标准——学会用它建立基线

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