☰
fio-3.8源码编译与IO性能压测核心参数精解
2026/10/10 19:55:59 网站建设 项目流程

简介:fio-3.8.zip 是面向系统工程师、存储性能测试人员及Linux内核/文件系统开发者的专业级I/O压测工具源码包,用于精准评估SSD、HDD、NVMe等存储介质在不同负载下的吞吐量、延迟与稳定性。资源共433个文件,以148个C源文件(backend.c、stat.c等核心模块)和164个头文件(h)构成完整可编译工程,辅以58个示例job配置、7个构建脚本(sh)、5个Python分析工具(如fio2gnuplot、fio_jsonplus_clat2csv)及详细manpage与howto文档,体现开箱即用的工程完整性。压缩包仅872KB,轻量但功能完备,涵盖从编译安装(make/make install)、多线程随机/顺序读写测试到结果可视化分析的全链路支持。目前已有1646人学习下载,读者可直接获取FIO 3.8稳定版源码、标准化测试模板、性能数据解析脚本及跨平台适配说明,快速开展存储栈调优、硬件选型验证或故障根因分析。

1. fio-3.8.zip 不是“又一个磁盘测速工具”:它是存储性能压测的底层黑匣子,专治IO虚标、缓存幻觉与配置玄学

你手头那台标称 7GB/s 的 NVMe SSD,在实际跑数据库导入时却卡在 1.2GB/s?Kubernetes 集群里 Pod 启动慢得像在加载古董 BIOS,iostat显示await高得离谱,但top里却找不到罪魁祸首?别急着换硬件或重装系统——这些症状,90% 源于你没真正“捅穿”存储栈:从应用层 IO 请求,到文件系统缓存策略,再到块设备队列深度、驱动超时、甚至固件内部的垃圾回收调度,中间横亘着至少五层可调参数。fio-3.8.zip 就是那个能一层层剥开、逐级施压、精准定位瓶颈的手术刀。它不是图形界面里点几下就出个“读写 MB/s”的玩具,而是一份带完整源码、可编译、可 patch、可嵌入 CI 流程的 C 语言级 IO 工作负载引擎。某实验室在验证新型 RDMA 存储网关时,正是靠修改 fio 的engines/rbd.c模块,注入自定义的延迟扰动逻辑,才复现并定位了内核rbd驱动中一个仅在 4K 随机写 + 低队列深度下触发的锁竞争缺陷。如果你需要的不是“大概快不快”,而是“在什么条件下、为什么、慢在哪一行代码”,那么 fio-3.8.zip 就是你必须亲手拆解、编译、调试的第一份真实资源。


2. 编译与基础验证:从源码包到可执行二进制,绕不开的三个关键依赖与两个隐式开关

fio-3.8.zip 是一个典型的 Unix 风格源码分发包,解压后结构清晰:./configure脚本、Makefile、fio.h头文件目录、以及按引擎(engines)、命令行解析(getopt)、日志(log)等逻辑划分的 C 源码子目录。它不提供预编译二进制,原因很实在——fio 的核心能力(如libaio异步 IO、rdma引擎、spdk绑定)高度依赖宿主环境的内核头文件、用户态库版本与 CPU 架构特性。直接make && make install很可能失败,且生成的二进制会缺失关键引擎。我们必须先理解其构建逻辑,再动手。

2.1 依赖检查:libaio-dev、zlib1g-dev与gcc-multilib的真实作用

fio 的./configure脚本会探测一系列系统库。其中三个最易被忽略但影响深远:

  • libaio-dev:这是启用libaio引擎(即真正的 Linux native AIO,非 glibc 模拟)的硬性前提。没有它,--ioengine=libaio会静默退化为sync,所有异步 IO 测试结果将完全失真。Debian/Ubuntu 下安装命令为sudo apt-get install libaio-dev;CentOS/RHEL 则为sudo yum install libaio-devel。
  • zlib1g-dev:它不仅用于压缩日志(--write_log),更关键的是支撑--ioengine=net(网络 IO 引擎)的流控与校验。若缺失,net引擎编译会跳过,但 configure 不报错,导致后续网络压测无法启用。
  • gcc-multilib:当你的测试目标是 32 位兼容模式(如某些老旧嵌入式存储控制器驱动)或需交叉编译时,此包提供i386-linux-gnu-gcc等多架构编译器。普通 x86_64 服务器可暂不装,但一旦遇到error: unknown type name ‘__u32’类编译错误,第一反应就是补上它。

提示:运行./configure --help | grep -E "(aio|zlib|net)"可快速确认当前环境探测到的引擎支持状态。输出中若含libaio: no,则必须先装libaio-dev并重新 configure。

2.2 配置阶段:--enable-experimental与--disable-rdma的取舍逻辑

fio-3.8 的./configure支持大量开关,但有两个对生产环境至关重要:

  • --enable-experimental:此开关默认关闭,但它解锁了io_uring引擎(Linux 5.1+ 内核必需)和mmap引擎的高级选项(如mmap的MAP_POPULATE预加载)。某公司在线上 MySQL 8.0 集群压测时,发现io_uring在高并发小 IO 场景下比libaio低 15% 延迟,正是通过开启此开关、编译带io_uring的 fio,才得以用--ioengine=io_uring --sqthread_poll参数组合复现问题。切记:开启后务必确认内核版本 ≥ 5.1,否则编译失败。
  • --disable-rdma:RDMA 引擎(rdma,rdma_pi)虽强大,但依赖libibverbs和rdma-core库,且在非 RoCEv2 网络环境下极易因ibv_create_cq失败而崩溃。若你压测目标仅为本地 NVMe 或 SATA SSD,强烈建议显式添加--disable-rdma。这能避免 configure 过程中因 RDMA 库版本不匹配导致的隐式禁用(configure 输出rdma: no (missing ibverbs)),让构建过程更干净、可复现。
# 推荐的生产环境编译流程(以 Ubuntu 22.04, kernel 5.15 为例) sudo apt-get update && sudo apt-get install -y \ build-essential libaio-dev zlib1g-dev \ libnuma-dev libssl-dev # 解压并进入源码目录 unzip fio-3.8.zip && cd fio-3.8 # 执行配置:启用 io_uring(因内核支持),禁用 RDMA(无 RDMA 网络) ./configure --enable-experimental --disable-rdma # 编译(-j$(nproc) 加速,但注意内存占用) make -j$(nproc) # 验证:检查关键引擎是否已编译进二进制 ./fio --engines=list | grep -E "(libaio|io_uring|sync)" # 正常输出应包含:libaio, io_uring, sync, psync, ...

这段make命令后,./fio即为可执行文件。--engines=list的输出是唯一可信的“我到底有什么”的依据——不要相信 configure 的文字提示,要信二进制自己说的。

2.3 快速验证:用--name=verify跑通一个最小闭环

编译成功不等于功能正常。必须用一个极简、无副作用的 job 文件验证整个链路:从参数解析、引擎初始化、到结果输出。我们创建verify.fio:

# verify.fio: 最小闭环验证脚本 [global] name=verify ioengine=sync # 使用最基础的同步引擎,规避所有异步依赖 rw=read # 只读,避免写坏测试盘 bs=4k # 标准块大小 size=1m # 总数据量仅 1MB,秒级完成 direct=0 # 不绕过 page cache,降低权限要求 filename=/tmp/fio.verify # 使用 /tmp,无需 root 权限 [job1] # 无需额外配置,继承 global
# 执行验证 ./fio verify.fio # 期望输出关键行(说明工作流完整): # verify: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=sync, iodepth=1 # verify: (g=0): minm/maxm=0/0, mint/mmax=0/0 # verify: (g=0): io=1024.0KB, aggrb=12345KB/s, minb=12345KB/s, maxb=12345KB/s, mint=83msec, maxt=83msec # verify: (g=0): bw ( KiB/s): min=12345, max=12345, per=100.00%, avg=12345.00, stdev=0.00 # verify: (g=0): lat (usec) : 250=0.01%, 500=0.02%, 750=0.03%, 1000=0.04% # verify: (g=0): lat (msec) : 2=99.85%, 4=0.05%, 10=0.01%, 20=0.01%, 50=0.01% # verify: (g=0): cpu : usr=0.12%, sys=0.45%, ctx=1234, majf=0, minf=56 # verify: (g=0): IO depths : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0% # verify: (g=0): IO submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% # verify: (g=0): IO complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% # verify: (g=0): issued r/w/d: total=256/0/0, short=0/0/0, drop=0/0/0 # verify: (g=0): latency : target=0, window=0, percentile=100.00%, depth=1

这个输出里,bw (KiB/s)行证明带宽计算正常;lat (usec/msec)行证明延迟统计模块工作;IO depths行证明队列深度逻辑生效;issued r/w/d行证明 IO 计数准确。只要这 15 行都出现,且无fio: pid=XXXX, err=XXXX, file: (null)类错误,就说明你的 fio-3.8 编译与基础运行环境 100% 可用。这是后续所有复杂压测的基石,跳过它,后面全是空中楼阁。


3. 核心参数精解:iodepth、numjobs与direct的三重耦合关系,及为何 90% 的人设错了

fio 的参数看似简单,实则是一个精密耦合系统。iodepth、numjobs、direct这三个参数,单独看都好懂,但放在一起,它们共同决定了 fio 如何向内核提交 IO 请求、内核如何调度、以及最终测出的数据代表哪一层的真实性能。绝大多数“测出来比厂商标称值低一半”的案例,根源都在这三个参数的误配。

3.1iodepth:不是“队列长度”,而是“每个 job 的未完成请求上限”

iodepth常被误解为“整个 fio 进程的 IO 队列深度”。这是致命错误。它的正确定义是:每个 job(由numjobs定义)独立维护的、向内核 block layer 提交但尚未完成的 IO 请求的最大数量。例如,numjobs=4且iodepth=32,意味着系统中最多同时存在4 * 32 = 128个未完成的 IO 请求(假设无其他限制)。

这个参数直接影响io_submit()系统调用的频率和block_rq_complete的中断压力。当iodepth过小(如 =1),fio 几乎退化为串行 IO,iostat中avgqu-sz(平均队列长度)会稳定在 1 附近,完全无法压满现代 SSD 的并行能力。当iodepth过大(如 >256),在libaio引擎下,io_submit()可能因内核aio-nr限制(/proc/sys/fs/aio-nr)而阻塞,导致fio自身线程被挂起,lat(延迟)曲线出现尖峰。

关键经验值:对于 NVMe SSD,iodepth应设为16 ~ 64;对于 SATA SSD,32 ~ 128;对于 HDD,64 ~ 256。这不是拍脑袋,而是基于设备queue_depth(cat /sys/block/nvme0n1/device/queue_depth)的 1/2 到 1/4 值。例如某 NVMe 设备queue_depth=256,则iodepth=64是合理起点。

3.2numjobs:不是“线程数”,而是“模拟的并发客户端数”

numjobs控制 fio 创建多少个独立的 worker 线程(或进程,取决于--group_reporting)。每个 worker 线程都拥有自己的iodepth队列、自己的文件描述符、自己的内存 buffer。它模拟的是应用层并发度。例如,一个 Web 服务器有 8 个 worker 进程,每个进程处理 16 个并发请求,那么numjobs=8且iodepth=16就是在模拟这个场景。

但numjobs与 CPU 核心数强相关。numjobs过大(如numjobs=64在 8 核机器上),会导致严重的线程上下文切换开销,cpu: sys%会飙升至 30% 以上,此时测出的bw已不是存储瓶颈,而是 CPU 调度瓶颈。反之,numjobs过小(如numjobs=1),即使iodepth=256,也只有一个线程在拼命填队列,无法体现多核并行下发 IO 的能力。

实操技巧:先固定iodepth=32,然后逐步增加numjobs(2→4→8→16),观察fio输出中的cpu: sys%。当sys%从 <5% 跃升至 >15%,说明线程调度开销已成瓶颈,此时的numjobs就是你的 CPU 上限。某公司在 32 核服务器上压测 Ceph RBD,发现numjobs=16时sys%=12%,numjobs=32时sys%=28%,最终选定numjobs=24作为平衡点。

3.3direct:绕过 page cache 的开关,但direct=1不等于“裸盘 IO”

direct=1的语义是:fio 的 buffer 分配使用posix_memalign(),并在open()时传入O_DIRECT标志,从而绕过内核 page cache,让 IO 直达 block layer。这确实是测“裸盘性能”的标准做法。

但direct=1有两大隐藏约束:

  • 内存对齐:buffer 地址和长度必须是getpagesize()(通常是 4096)的整数倍。fio 内部会自动对齐,但如果你用--alloc-size指定了非对齐值,direct=1会静默失败,回退到direct=0。
  • 文件系统支持:XFS 默认支持O_DIRECT,但 ext4 在某些旧内核(<4.14)下,若文件系统挂载时未加dax或barrier=0选项,O_DIRECT可能被内核拦截并降级。

因此,验证direct是否真正生效,不能只看参数,要看fio输出的ioengine行:

  • ioengine=libaio, iodepth=32, direct=1→ 正确
  • ioengine=libaio, iodepth=32, direct=0→ 失败,page cache 生效
# 错误示范:未对齐的 alloc-size 导致 direct 失效 [global] direct=1 alloc-size=1000000 # 1000000 % 4096 != 0,触发回退 # 正确写法:显式指定对齐 [global] direct=1 alloc-size=1048576 # 1024*1024 = 1MB,完美对齐 4K

3.4 三参数耦合:一个必须掌握的黄金公式

iodepth、numjobs、direct的终极目标,是让fio的 IO 模式尽可能贴近你的真实业务负载。为此,我们总结出一个可落地的黄金公式:

iodepth × numjobs ≈ 设备 queue_depth × 并发应用实例数

  • 设备 queue_depth:cat /sys/block/<dev>/device/queue_depth
  • 并发应用实例数:你的数据库有 8 个连接池,每个池最大连接数 100,则≈ 8(因为连接池是复用的,不是每个连接都持续 IO)

例如,测试一块queue_depth=256的 NVMe,目标模拟 4 个 PostgreSQL 实例:

  • 初步设定:iodepth=64,numjobs=4→64×4=256,完美匹配。
  • 若direct=0,测出bw=3.2GB/s,说明 page cache 效果显著;
  • 若direct=1,测出bw=6.8GB/s,这才是设备真实吞吐。

记住:没有“标准参数”,只有“匹配你场景的参数”。把iodepth=128、numjobs=1和iodepth=16、numjobs=8对比,前者是单线程狂灌,后者是八线程协作,它们测出的lat曲线形态、cpu: sys%、甚至iostat中的svctm(服务时间)都完全不同。选哪个,取决于你要回答的问题。


4. 避坑:fio-3.8 常见翻车现场与血泪排查指南

fio-3.8 功能强大,但因其深度绑定内核与硬件,新手极易掉进一些隐蔽深坑。这些坑往往不报错,只给一个“看起来不对”的结果,让人反复怀疑是不是硬盘坏了、是不是线缆松了。以下是我在多个项目中踩过的、最典型、最高频的五个问题,每一条都附带现象、根因与一招解决。

4.1 现象:fio进程 CPU 占用 100%,但iostat显示r/s和w/s为 0,await无限高

原因:iodepth设置过大,超过了内核aio-max-nr限制,导致io_submit()系统调用被阻塞,fio worker 线程在用户态死循环等待内核返回,CPU 空转。
解决:

  1. 查看当前限制:cat /proc/sys/fs/aio-max-nr(通常为 65536)
  2. 计算理论最大iodepth × numjobs,确保< aio-max-nr
  3. 临时提升限制:echo 131072 | sudo tee /proc/sys/fs/aio-max-nr
  4. 永久生效:echo "fs.aio-max-nr = 131072" | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

4.2 现象:fio报错fio: file /path/to/testfile: failed to create file: Permission denied,但ls -l显示目录可写

原因:direct=1时,fio 需要O_DIRECT权限,而某些文件系统(如 overlayfs、某些 NFS 版本)或挂载选项(noexec,nosuid)会禁止O_DIRECT。
解决:

  • 先用direct=0测试是否能创建文件,确认是direct问题
  • 将测试文件放在ext4或xfs本地文件系统上
  • 检查挂载选项:findmnt -t ext4,xfs,确保无dax或strictatime等干扰项
  • 终极方案:改用--filename=/dev/nvme0n1p1(裸设备),彻底绕过文件系统

4.3 现象:fio输出lat (usec)全是0=100.00%,bw数值巨大(如 50GB/s)

原因:direct=0且测试文件已在 page cache 中,所有读操作都命中 cache,fio测的是内存带宽,不是磁盘。
解决:

  • 清空 page cache:sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"
  • 确保filename是一个新文件(或用--create-destroy=1让 fio 自动创建销毁)
  • 永远在direct=1下做性能基准测试,direct=0仅用于分析 cache 效果

4.4 现象:fio运行几分钟后突然退出,dmesg显示blk_update_request: I/O error, dev nvme0n1, sector XXXXX

原因:SSD 固件 bug 或硬件故障,在高压力下触发了不可恢复的 IO 错误。fio 默认ioengine=libaio会将此类错误视为 fatal,立即终止。
解决:

  • 添加--ignore_error=none(默认)改为--ignore_error=io,让 fio 忽略单次 IO 错误继续运行
  • 同时添加--replay-time=300(记录前 5 分钟 trace),便于事后用fio_generate_plots分析错误发生前的 pattern
  • 立即停用该盘,用smartctl -a /dev/nvme0n1检查Critical Warning和Media and Data Integrity Errors

4.5 现象:fio使用io_uring引擎时,lat曲线出现周期性 10ms~100ms 的尖峰

原因:io_uring的sqthread_poll模式依赖内核线程轮询,若该线程被其他高优先级任务(如实时进程、NMI watchdog)抢占,就会导致延迟毛刺。
解决:

  • 关闭 NMI watchdog:echo 0 | sudo tee /proc/sys/kernel/nmi_watchdog
  • 将io_uring线程绑定到隔离 CPU:taskset -c 1-3 ./fio job.fio --ioengine=io_uring --sqthread_poll
  • 或者,放弃sqthread_poll,改用--ioengine=io_uring --registerfiles=1(注册文件描述符,减少每次 IO 的 syscall 开销)

5. 进阶技巧:用--write_log生成 IO Trace,并用fio_generate_plots可视化分析随机性与热点

fio 的终极价值,不仅在于给出一个bw=5.2GB/s的数字,更在于它能生成精确到微秒级的、完整的 IO 请求轨迹(trace)。这份 trace 是诊断“为什么慢”的黄金证据,远胜于iostat的 1 秒聚合。fio-3.8 内置了--write_log参数,配合官方工具fio_generate_plots,可以将枯燥的数字转化为直观的热力图与分布图,一眼锁定随机写放大、读写干扰、长尾延迟等顽疾。

5.1 生成 IO Trace:--write_log的正确姿势与文件格式

--write_log并非简单地把日志写到文件,它有三种模式,必须根据分析目标选择:

模式参数写法生成文件适用场景
延迟日志--write_log=latjobname_lat.log分析延迟分布、长尾、P99/P999
IOPS 日志--write_log=iopsjobname_iops.log分析吞吐稳定性、秒级波动、突发峰值
详细 trace--write_log=tracejobname_trace.log分析 IO 模式:读/写比例、偏移量分布、请求大小分布

关键注意:--write_log会显著降低 fio 性能(约 20%~40%),因为它要同步写磁盘。因此,永远不要在--runtime很长的压测中全程开启。推荐做法是:先用短时--runtime=60跑一次,开启--write_log=trace,获取代表性样本。

# trace-job.fio:生成详细 IO trace 的专用 job [global] name=trace-test ioengine=libaio rw=randwrite bs=4k size=2g runtime=60 time_based direct=1 iodepth=64 numjobs=4 filename=/dev/nvme0n1p1 write_log=trace # 关键:生成 trace log_avg_msec=1000 # 每秒汇总一次统计,减少日志量 [trace-job] # 无需额外配置
# 执行并生成 trace ./fio trace-job.fio # 查看生成的 trace 文件(文本格式,人类可读) head -n 5 trace-test_trace.log # 输出示例: # 1672531200.123456 1 0 4096 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0...... # 第一列是时间戳(秒.微秒),第二列是 job number,第三列是 rw(0=read,1=write),第四列是 size(bytes)...

5.2 可视化分析:用fio_generate_plots将 trace 转为热力图与分布图

fio 源码包中自带tools/fio_generate_plots脚本(需 Perl 和 gnuplot)。它能将lat.log或iops.log转为 PNG 图片,但对trace.log的支持需要额外步骤。我们以lat.log为例,展示如何快速获得专业级分析图:

# 1. 先生成 lat.log(比 trace.log 轻量,适合快速分析) ./fio --name=lat-test --ioengine=libaio --rw=randread --bs=4k \ --size=1g --runtime=30 --time_based --direct=1 \ --iodepth=64 --numjobs=4 --filename=/dev/nvme0n1p1 \ --write_log=lat # 2. 进入 tools 目录,运行绘图脚本 cd tools ./fio_generate_plots -t "NVMe RandRead Latency" \ -o /tmp/latency.png \ ../lat-test_lat.log # 3. 查看生成的 /tmp/latency.png —— 这是一张标准的延迟累积分布图(CDF) # X轴:延迟(usec),Y轴:百分位(%),曲线越陡峭,说明延迟越集中

这张 CDF 图的价值在于:

  • 若 P99 延迟是 100us,P99.9 是 10ms,说明有 0.1% 的请求被严重拖慢,需排查是否由 GC、坏块重映射或驱动 bug 引起;
  • 若曲线在 500us 处突然变平,说明设备有“延迟平台期”,这是 TLC/QLC SSD 的典型特征;
  • 若整条曲线右移(如 P50 从 80us → 200us),则可能是 CPU 频率降频、温度 throttling 或 queue full。

对于更深入的trace.log分析,我一般会用 Python + pandas 做定制化处理:

# analyze_trace.py:提取偏移量分布,识别热点区域 import pandas as pd import matplotlib.pyplot as plt # 读取 trace.log,跳过注释行,按空格分割 df = pd.read_csv('trace-test_trace.log', sep=r'\s+', header=None, comment='#', names=['time', 'job', 'rw', 'size', 'offset', '...']) # 过滤出写操作(rw==1),计算 offset 的 GB 级别分布 df_write = df[df[2] == 1].copy() df_write['gb_offset'] = (df_write[4] / (1024**3)).round(1) # 转 GB # 绘制热力图:X轴为时间(秒),Y轴为 offset(GB),点大小为 size plt.figure(figsize=(12, 6)) plt.scatter(df_write['time'] - df_write['time'].iloc[0], df_write['gb_offset'], c=df_write[3], s=df_write[3]/100, alpha=0.6, cmap='viridis') plt.colorbar(label='IO Size (bytes)') plt.xlabel('Time (seconds)') plt.ylabel('Offset (GB)') plt.title('IO Access Pattern Heatmap: Hotspots Revealed') plt.savefig('/tmp/trace_heatmap.png', dpi=300, bbox_inches='tight') plt.show()

这段代码生成的热力图,能清晰显示:

  • 是否存在某个 GB 区域(如offset=10.5GB)被反复擦写(热点);
  • 读写是否交织(Y轴上红蓝点混杂),这会导致 SSD 内部读写干扰;
  • IO 大小是否高度不均(点大小差异大),暗示应用层 buffer 管理有问题。

5.3 一个真实案例:用 trace 定位 MySQL 的 double-write buffer 写放大

某公司 MySQL 5.7 实例在高并发写入时,iostat显示w/s是业务 SQL QPS 的 3 倍,怀疑有写放大。我们用 fio 模拟其innodb_page_size=16k、innodb_log_file_size=256M的典型负载:

# mysql-sim.fio [global] name=mysql-sim ioengine=libaio rw=randwrite bs=16k size=10g runtime=120 time_based direct=1 iodepth=128 numjobs=8 filename=/dev/nvme0n1p1 write_log=trace log_avg_msec=1000 [mysql-job]

生成mysql-sim_trace.log后,用上述 Python 脚本绘图,发现:

  • 在offset=0~256MB区域(对应 ib_logfile0),出现密集、周期性(每 1 秒一次)、大小固定为16k的写入簇;
  • 同时,在offset=10~20GB区域(数据文件),出现大量16k写入,但无规律;

这完美匹配 MySQL 的 double-write buffer 机制:每次 checkpoint,先写 double-write buffer(固定位置、固定大小),再写数据页(随机位置)。fio的 trace 让我们无需动 MySQL 配置,就直接“看见”了写放大的物理源头。后续通过SET GLOBAL innodb_doublewrite=OFF(仅测试环境)验证,w/s降为 QPS 的 1.2 倍,证实判断。

从那以后我每次做数据库存储压测,都强制走一遍--write_log=trace+fio_generate_plots流程,哪怕多花 5 分钟。因为数字会骗人,但 trace 不会——它记录了每一个字节被送往何方、何时发出、耗时多久。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询