基于华为云 FlexusX 四节点集群的云计算全栈实操(三):云硬盘 IO 压测(fio 全场景读懂 IOPS 与吞吐)

基于华为云 FlexusX 四节点集群的云计算全栈实操(三):云硬盘 IO 压测(fio 全场景读懂 IOPS 与吞吐)

前两篇我们搭好了实验室、给 CPU/内存做了体检。本篇把目光投向最容易「翻车」的 storage:云硬盘的 IOPS 和吞吐到底是多少?为什么数据库和对象存储对云盘的要求天差地别?我们用 fio 跑五个场景,把数字扒开。

0. 引子:云上最容易被低估的瓶颈

很多性能问题,最后都落在磁盘上。应用层调得再好,如果云盘 IOPS 不够、尾延迟爆炸,MySQL 的慢查询、Kafka 的刷盘停顿、容器镜像的拉取都会跟着抖。问题是——云盘的性能是「分场景」的:顺序大块读写看吞吐(MiB/s),随机小块读写看 IOPS。买错类型,钱花三倍体验还差。

《深入浅出云计算》把存储分为「块存储 / 对象存储 / 文件存储」。本篇聚焦最底层的块存储(云硬盘),用 fio 量化它的能力边界,并给出选型与成本权衡。

1. 理论:先分清几个容易混的概念

  • IOPS(Input/Output Operations Per Second):每秒能完成多少次 IO。小块(如 4KiB)随机读写时,IOPS 是天花板。
  • 吞吐(Throughput,MiB/s):每秒搬多少数据。大块(如 1MiB)顺序读写时,吞吐是天花板。
  • 二者的关系吞吐 ≈ IOPS × 块大小。4KiB 随机读 8000 IOPS ≈ 31 MiB/s;1MiB 顺序读 120 IOPS ≈ 120 MiB/s。块越大,相同 IOPS 下吞吐越高。
  • iodepth:单个进程的「在途 IO 深度」。云盘后端是队列,iodepth 太小喂不饱,太大则排队延迟上升。
  • numjobs:并发进程数。多进程 + 适当 iodepth,才能压出云盘标称峰值。

一句话:小块看 IOPS,大块看吞吐;要压满,得靠 iodepth × numjobs 把队列喂饱。

2. 实验环境

四节点规格同上篇(8vCPU/16GiB,Ubuntu 24.04.4 LTS)。本次被测盘为各节点的40G 系统盘/dev/vda(注意:是系统盘,非独立数据盘,性能口径需结合这一点看)。压测在 node1、node3 两个节点采样。

==== 云硬盘信息 ==== NAME SIZE ROTA SCHED vda 40G 1 none

ROTA=1是 lsblk 对 virtio 块设备的常规标记(不代表真机械盘),SCHED=none说明使用了无队列调度的 virtio 路径。

3. 实操:fio_bench.sh 五个场景

我用scripts/fio_bench.sh一次性跑完五个场景,--direct=1绕过页缓存、测真实盘,libaio异步引擎,time_based+runtime=20取稳态。关键参数如下:

# 顺序写:1M 大块,iodepth=32fio--name=seqwrite--directory=/root/fiotest--ioengine=libaio--direct=1--rw=write\--bs=1M--iodepth=32--numjobs=1--size=2G--runtime=20--time_based--group_reporting# 顺序读:1M 大块,iodepth=32fio--name=seqread--directory=/root/fiotest--ioengine=libaio--direct=1--rw=read\--bs=1M--iodepth=32--numjobs=1--size=2G--runtime=20--time_based--group_reporting# 随机写:4k 小块,iodepth=64,4 进程fio--name=randwrite--directory=/root/fiotest--ioengine=libaio--direct=1--rw=randwrite\--bs=4k--iodepth=64--numjobs=4--size=512M--runtime=20--time_based--group_reporting# 随机读:4k 小块,iodepth=64,4 进程fio--name=randread--directory=/root/fiotest--ioengine=libaio--direct=1--rw=randread\--bs=4k--iodepth=64--numjobs=4--size=512M--runtime=20--time_based--group_reporting# 混合读写 70/30:读占 70%fio--name=randrw--directory=/root/fiotest--ioengine=libaio--direct=1--rw=randrw--rwmixread=70\--bs=4k--iodepth=64--numjobs=4--size=512M--runtime=20--time_based--group_reporting

并行下发:

PYTHONPATH=deps python tools/fanout.py n1,n3'bash /root/fio_bench.sh'

4. 真实输出(来自 results/03_fio_bench.txt)

node1 (113.47.6.41)

==== [1] 顺序写 bs=1M iodepth=32 ==== write: IOPS=125, BW=126MiB/s (132MB/s)(2550MiB/20298msec) ==== [2] 顺序读 bs=1M iodepth=32 ==== read: IOPS=117, BW=118MiB/s (123MB/s)(2432MiB/20672msec) ==== [3] 随机写 bs=4k iodepth=64 numjobs=4 ==== write: IOPS=8152, BW=31.8MiB/s (33.4MB/s)(653MiB/20508msec) clat percentiles (usec): | 99.00th=[ 968885] ==== [4] 随机读 bs=4k iodepth=64 numjobs=4 ==== read: IOPS=8094, BW=31.6MiB/s (33.2MB/s)(657MiB/20782msec) clat percentiles (usec): | 99.00th=[ 977273] ==== [5] 混合读写 70/30 bs=4k ==== read: IOPS=5655, BW=22.1MiB/s (23.2MB/s)(459MiB/20787msec) write: IOPS=2437, BW=9750KiB/s (9984kB/s)(198MiB/20787msec)

node3 (1.94.220.182)

==== [1] 顺序写 bs=1M iodepth=32 ==== write: IOPS=126, BW=127MiB/s (133MB/s)(2551MiB/20141msec) ==== [2] 顺序读 bs=1M iodepth=32 ==== read: IOPS=120, BW=121MiB/s (126MB/s)(2473MiB/20500msec) ==== [3] 随机写 bs=4k iodepth=64 numjobs=4 ==== write: IOPS=8071, BW=31.5MiB/s (33.1MB/s)(653MiB/20700msec) clat percentiles (usec): | 99.00th=[ 968885] ==== [4] 随机读 bs=4k iodepth=64 numjobs=4 ==== read: IOPS=8089, BW=31.6MiB/s (33.1MB/s)(657MiB/20791msec) clat percentiles (usec): | 99.00th=[ 977273] ==== [5] 混合读写 70/30 bs=4k ==== read: IOPS=5651, BW=22.1MiB/s (23.1MB/s)(459MiB/20788msec) write: IOPS=2437, BW=9748KiB/s (9982kB/s)(198MiB/20788msec)

汇总对比

场景指标node1node3
顺序写 (1M)吞吐126 MiB/s (125 IOPS)127 MiB/s (126 IOPS)
顺序读 (1M)吞吐118 MiB/s (117 IOPS)121 MiB/s (120 IOPS)
随机写 (4k)IOPS / 吞吐8152 / 31.8 MiB/s8071 / 31.5 MiB/s
随机读 (4k)IOPS / 吞吐8094 / 31.6 MiB/s8089 / 31.6 MiB/s
混合 70/30 (4k)读 IOPS / 写 IOPS5655 / 24375651 / 2437

两节点几乎一致,符合系统盘的同质预期。

5. 深度解读(重点)

5.1 吞吐 vs IOPS:同一块盘的两个面

看这张表最直观的结论:大块顺序场景,吞吐约 120 MiB/s、IOPS 只有 ~120;小块随机场景,IOPS 冲到 ~8000、吞吐却只有 ~31 MiB/s。同一个盘,换了块大小,天花板指标就从「吞吐」切换成「IOPS」。

验证公式吞吐 = IOPS × 块大小:随机读 8094 IOPS × 4KiB ≈ 31.6 MiB/s ✓;顺序读 120 IOPS × 1MiB ≈ 120 MiB/s ✓。两张面孔,本质是一回事——盘后端的「并发能力」与「单请求搬运量」的乘积。

5.2 为什么顺序读略低于顺序写(这里反直觉)

一般企业云盘是「写有缓存/写合并、读要落盘」所以写快于读;本环境顺序写 126 MiB/s、顺序读 118–121 MiB/s,读略低于写。这是 40G 系统盘的限速画像——系统盘的定位就是「装系统和偶尔读」,顺序读带宽被刻意压在比写更低的档位。规格选型时若需要高顺序读(如训练数据集加载),应换高吞吐型/SSD 数据盘。

5.3 iodepth 与 numjobs:为什么大块用 iodepth、小块用 iodepth×numjobs

  • 顺序 1M 场景:numjobs=1, iodepth=32。单个大块请求本身就很「重」,一个进程靠 iodepth=32 把队列喂满就够了,再多加进程收益不大。
  • 随机 4k 场景:numjobs=4, iodepth=64。4KiB 请求极轻,单进程即使 iodepth=64 也可能喂不饱后端(请求处理太快、队列空窗)。用 4 个进程 × 64 深度 = 256 条在途 IO,才把 IOPS 压到 ~8000 的天花板。

经验:大块看单进程 iodepth,小块看 进程数×iodepth。压随机小 IO 时,只调 iodepth 不调 numjobs,很容易测出一个「假低点」然后误判云盘不行。

5.4 尾延迟(clat 99th):被忽略的雷

随机写的clat 99th = 968885 usec(≈ 969 ms),随机读99th = 977273 usec(≈ 977 ms)。这是 99 分位的完成延迟,接近 1 秒。均值很漂亮(4KiB 随机 ~8000 IOPS,平均延迟约 0.8ms),但尾部会蹿到近 1 秒。

这说明:这块 40G 系统盘在压力下的长尾延迟极不稳定。对延迟敏感的在线服务(数据库事务提交、KV 读),p99 可能会很难看。根因通常是共享存储后端在队列拥塞时的调度——这也是「系统盘」不适合扛核心数据库的根本原因之一。

5.5 混合 70/30 的意义

真实业务极少是纯读或纯写。混合 70/30 下:读 5655 IOPS、写 2437 IOPS。注意读 IOPS 从纯随机读的 8094 掉到 5655——因为读写互相争用同一个 IO 队列和盘带宽,叠加写放大效应,整体吞吐被写拖慢。这提醒我们:压测别只跑纯读/纯写,混合比例才接近生产。

6. 数据库 / 日志 / 大文件:云盘选型与成本权衡

基于上面的画像,给三类典型负载的选型建议:

负载类型关键指标应选云盘成本权衡
数据库(MySQL/PG)随机 4k IOPS、低尾延迟高 IOPS SSD 云盘(如超高 IO 型),并用独立数据盘而非系统盘贵,但 IOPS/尾延迟直接决定 QPS 与 p99;系统盘跑 DB 是禁忌。
日志 / Kafka / 写放大型顺序写吞吐 + 一定 IOPS高吞吐型或通用 SSD顺序写本盘 ~126 MiB/s 可用;量极大时按吞吐计费更划算。
大文件 / 镜像 / 训练集顺序读吞吐高吞吐型 + 大块读顺序读 ~120 MiB/s 偏弱,需高吞吐盘或并行多盘条带。

成本观点:云盘选型的第一原则,是让「负载的瓶颈指标」匹配「云盘的峰值指标」。随机小 IO 的库别买吞吐盘(花钱买用不上的 MiB/s),大文件服务别买 IOPS 盘(花钱买用不上的 IOPS)。另外,系统盘(本环境的 40G vda)只该装系统——任何有性能要求的存储,都请挂独立数据盘。本次 8000 IOPS 的「好看数字」背后是近 1 秒的尾延迟,绝不适合直接扛核心数据库。

5.6 一个常见误判:拿系统盘当数据盘

本环境 40G vda 是系统盘,它有明确的「装系统」定位:顺序读被压在 ~120 MiB/s、随机 IO 尾延迟近 1 秒。很多新手把数据库 data 目录直接放在系统盘,结果 QPS 上不去、偶尔卡顿查不出原因——根子就在这块盘的画像上。正确做法永远是:系统归系统,数据挂独立云盘,按需选高 IOPS / 高吞吐型,并把 IOPS 和吞吐的「突发+基准」额度算进容量规划。

7. 踩坑与排障

  • --direct=1一定要开:不开会走页缓存,测出的是内存带宽不是盘速,数字虚高。
  • 不要在系统盘根分区直接写:脚本用/root/fiotest临时目录并最终rm -rf,避免污染与占满。
  • iodepth 过大导致延迟飙升:本文 64 已偏高,若再加大,IOPS 不涨、clat 反而更差,说明已到后端队列极限。
  • 多节点采样差异:本次仅 n1/n3 采样即高度一致;正式选型建议覆盖更多节点取中值,排除偶发邻居干扰。

8. 配套脚本

  • ../scripts/fio_bench.sh:本篇五个场景的完整 fio 脚本,可直接fanout下发。
  • ../tools/ssh_run.py../tools/fanout.py:结果采集与并行下发。

9. 小结

本篇用 fio 把 40G 系统盘的真实成色扒开,三条核心结论:

  1. 吞吐与 IOPS 是同一盘的两个面:顺序 1M 约 120 MiB/s(~120 IOPS),随机 4k 约 8000 IOPS(~31 MiB/s),公式吞吐=IOPS×块大小完全吻合;
  2. 压随机小 IO 必须用numjobs×iodepth:单调 iodepth 会测出假低点;本环境 4 进程×64 深度才压出 ~8000 IOPS;
  3. 尾延迟是隐藏雷:随机 IO 的 99th clat 接近 1 秒,系统盘绝不适合扛核心数据库——按负载瓶颈指标选独立数据盘,才是正解。

IaaS 三篇到此收官(开篇基建、CPU/内存、云硬盘)。下个阶段我们将进入PaaS 层:对象存储、负载均衡、RDS vs 自建,把「计算 + 存储」真正用成服务。