Storpath
L1 系统基础全程第 11 / 38 节看完整路径
实验预计 45 分钟

观测与压测工具箱

把 iostat/vmstat/perf/bpftrace 和 elbencho 串成一套可复用的手法。

学完这节你能做到

  • 用 elbencho 设计出能回答具体问题的测试用例,而不是跑个分
  • 用 elbencho service 模式对共享文件系统做多客户端压测
  • 读懂 first done / last done 两列,从差距里看出不均衡
  • 用 bpftrace 抓出单次慢 I/O 的调用栈

把前面几节串成一套手法

到这里,L0 的知识点已经齐了。这一节不引入新概念,而是把工具组织成可复用的动作:遇到什么问题,按什么顺序,敲哪些命令。

工具分两类,代价完全不同:

类型代表开销什么时候用
静态计数器vmstat、iostat、sar、ss几乎为零常态巡检、第一轮定位
动态追踪perf、bpftrace、biosnoop有开销,需谨慎定位到范围后,抓具体证据

顺序永远是:先用零开销的工具收敛范围,再用追踪工具挖根因。反过来做,你会在生产机器上跑一个高开销的 trace,然后被业务方投诉。

压测:elbencho 的四件套

压测不是"跑个分",是回答一个具体问题。先想清楚问题,再选参数。

本课统一用 elbencho —— 它由 BeeGFS 创始人开发,一个工具覆盖了 fio、mdtest、ior 的功能,块设备、文件系统、S3 都能压,而且原生支持多客户端协同。学一套参数就够用。

# 1. 顺序写 —— 回答"这块盘的写带宽上限是多少"
elbencho -w --direct -b 1M -t 4 --iodepth 32 \
         -s 100g --timelimit 60 --lat /dev/nvme0n1

# 2. 顺序读 —— 回答"读带宽上限"
elbencho -r --direct -b 1M -t 4 --iodepth 32 -s 100g --timelimit 60 /dev/nvme0n1

# 3. 随机写 —— 回答"数据库类负载能跑多少 IOPS"
elbencho -w --direct -b 4k -t 4 --iodepth 64 --rand --randamount 50g \
         -s 100g --timelimit 60 --lat --latpercent /dev/nvme0n1

# 4. 随机读 —— 回答"元数据类负载能跑多少 IOPS"
elbencho -r --direct -b 4k -t 4 --iodepth 64 --rand --randamount 50g \
         -s 100g --timelimit 60 --lat --latpercent /dev/nvme0n1

必须理解的几个参数:

参数含义常见错误
--direct绕过 page cache忘了加,测出的是内存速度
-b块大小不写块大小的 IOPS 数字没有意义
--iodepth单线程异步队列深度(默认 1 = 同步 I/O)用默认值会低估 NVMe 的能力
-t并发线程数单线程压不满 NVMe
--rand --randamount随机 offset,以及每线程的随机 I/O 总量不加 --rand 测的其实是顺序
--timelimit按时间跑而非按数据量不加就是写完 -s 停,可能只测到 SSD 的缓存区间
--lat --latpercent输出延迟均值与百分位只报 IOPS 不报延迟,等于没测
×三个让压测结果失真的经典错误
  1. 忘了 --direct —— HDD 跑出 3GB/s,测的是 page cache
  2. 测试时长太短 —— SSD 有 SLC 缓存,前 30 秒很快,之后掉到稳态。至少 --timelimit 300 才能看到真实的稳态性能
  3. 在生产盘上跑写测试 —— 路径写成 /dev/nvme0n1 时 -w 会直接覆盖裸设备上的数据。动手前先确认这块盘是空的

压测:多客户端压共享文件系统

单机压出来的数字只是上限参考。共享文件系统(CephFS、GPFS)需要多客户端并发才能压出真实能力——同一个 elbencho,加上 service 模式即可:

# 每台客户端上先起一个 service(后台常驻)
elbencho --service

# 协调节点统一发起:4 台客户端一起压同一个文件系统
elbencho --hosts client1,client2,client3,client4 \
         -w -b 4M -t 16 --direct -s 100g /mnt/cephfs/bench/file

# 多文件(元数据)性能:这才是共享文件系统真正的考验
elbencho -w -d -n 100 -N 10000 -s 4k -t 16 /mnt/cephfs/bench

# 块设备:一次压多块盘,验证整机能力
elbencho -w -b 4M -t 48 --direct -s 100g /dev/nvme{0..11}n1
✓裸盘压测顺便验证 PCIe 是否掉速

压测裸盘时同步开一个 iostat -xm 1 观察。经验值:NVMe 单盘写入约 5 GB/s,如果实测差得远,先怀疑 PCIe 链路降速:

lspci -vvv | grep 'Non-Volatile'          # 拿到 PCIe 地址,如 5a:00.0
lspci -s 5a:00.0 -vvv | grep 'Speed'
#   LnkCap: Speed 16GT/s, Width x4
#   LnkSta: Speed 16GT/s (ok), Width x1 (downgraded)   ← 掉到 x1 了

出现 downgraded 说明链路宽度或速率没跑满,通常是插槽、转接卡或 BIOS 设置的问题。这类硬件问题必须在集群部署前发现,否则上线后会表现为某几个 OSD 莫名其妙地慢。

结果怎么读:first done 与 last done

elbencho 的结果表有两列数字,很多人只看右边一列就走了,其实这两列之间的差距本身就是结论:

OPERATION RESULT TYPE        FIRST DONE  LAST DONE
========= ================== ==========  =========
WRITE     Elapsed ms       :      41230      68940
          IOPS             :      12470       7460
          Throughput MiB/s :       4870       2910
          Total MiB        :     200704     200704
  • first done = 第一个工作线程(多客户端时是第一个 host)干完自己那份活时的统计。此刻所有线程都还在跑,机器处于满载、竞争均衡的状态,所以这一列代表并发拉满时的峰值能力。
  • last done = 最后一个线程干完时的统计,也就是整个作业的真实完成时间。这一列包含了后半段"快的已经收工、慢的还在磨"的拖尾,所以它是你真正能对外承诺的聚合数字。

上面这组数据里 last done 的带宽只有 first done 的六成,说明最快和最慢的线程差了 27 秒——这不是一次可信的压测,是一次暴露了不均衡的压测。

一句话记法:想知道"这堆盘、这张网到底能跑多快",看 first done;想知道"这批活干完要多久",看 last done。

✓两列各有各的用途,不要只留一个
  • 判断硬件上限用 first done,它更接近设备/网络的理论峰值
  • 描述业务感知("全部完成要多久")用 last done
  • 两列的差距用来判断这次测试有没有资格被当成基线:经验阈值是 elapsed 差 10% 以内正常,超过 20% 先查不均衡,别急着记数字
✓判断硬件上限、做验收对比理论值,一定用 first done

硬件验收要回答的是"这批盘和这张网有没有跑到它该有的水平",这时只有 first done 有意义——它是所有线程都还在跑、机器满载时的数字,最接近设备与链路的物理峰值。last done 已经被拖尾稀释过,拿它对比理论值会系统性地低估硬件。

举个例子:12 块 NVMe 单盘写 5 GB/s,理论上限约 60 GB/s;节点是 2 × 25GbE,网络上限约 6 GB/s。

  • first done 4.9 GB/s:达到网络理论值的 80% 以上 → 硬件没问题,瓶颈在网络,符合预期,可以验收
  • 如果拿 last done 的 2.9 GB/s 去比,只有网络上限的一半 → 会得出"硬件不达标"的错误结论,然后开始排查根本不存在的问题

所以顺序是:先用 first done 确认硬件跑到了该有的水平,再用两列的差距确认这次运行是否均衡,最后才用 last done 描述业务感知。

差距过大的常见原因

类别具体原因怎么确认
测试数据量太小每线程只跑几秒,预热、文件创建/打开、缓存刷写这些固定开销占了大头;线程间几秒的正常抖动被放大成几十个百分点把 -s / --randamount 加大到每线程至少跑 60 秒以上,看差距是否收敛
任务切分不均文件大小不一、-N 除不尽线程数、目录里文件数量差异大换成等大文件、线程数取整重跑一遍看差距是否消失
有一块慢盘单盘 PCIe 降速、坏道重试、温度降频、SSD 寿命末期iostat -xm 1 看哪块盘 await 高;ceph osd perf 找慢 OSD;lspci 查 downgraded
数据分布不均Ceph 的 PG / CRUSH 权重不均、OSD 接近 nearfull、GPFS NSD 容量不一致ceph osd df tree 看 PG 数与使用率的离散度
元数据侧串行所有线程写同一个目录,撞在同一个 MDS / 同一个元数据锁上用 -d -n 让每线程写自己的目录,对比差距
客户端不对等某台客户端 CPU 少、挂载参数不同、跨了交换机、bond 哈希不均、MTU 不一致逐台单独跑一遍取单机基线,横向比对;ethtool -S 看丢包重传
SSD 缓存耗尽 / GC前 30 秒吃 SLC 缓存很快,之后掉稳态,先跑完的线程占了便宜拉长 --timelimit 或加大数据量,看差距是否收敛
缓存命中不对等部分线程的数据还在 page cache 里加 --direct,或每轮前 --dropcache
后台任务抢资源测试期间集群在 scrub / recovery / backfill / 快照回收压测前 ceph -s 确认 HEALTH_OK 且无 recovery
启动不同步service 模式下各 host 起跑时间差、节点间时钟偏移用协调节点统一发起(--hosts),别手工分别敲命令
!写报告不能直接抄 last done

last done 只是这一次运行的完成时间,它把所有拖尾原封不动地算进去了。如果差距来自数据量太小、某块慢盘或后台 recovery,那这个数字描述的是那次故障/那个测试缺陷,不是系统能力——直接抄进报告,等于把偶然当结论,而且换台机器就复现不出来。

出数字之前按顺序做四件事:

  1. 先看差距。超过 20% 不出数字,先把原因解决掉或解释清楚,再重跑一轮
  2. 确认量够。写清每线程跑了多久、总数据量多少;跑了几秒的结果没有资格进报告
  3. 两列都写,附上差距百分比和一句原因说明。只给一个数字,一定会被追问"这是峰值还是平均"
  4. 重复 3 次取中位数。三次 last done 波动超过 10%,说明还没进稳态,任何单次数字都不是基线

给结论时用区间比用一个点更诚实:稳态聚合带宽 2.9 ~ 4.9 GB/s(last / first done),差距源于 osd.23 慢盘,已换盘后复测。

×用 --timelimit 会把不均衡藏起来

--timelimit 是按时间跑,所有线程同时被叫停,first done 和 last done 自然几乎相等——差距消失不等于不均衡消失,它只是换了个形式:表现为总吞吐比预期低。所以定位不均衡时要跑固定数据量(只给 -s / --randamount,不加 --timelimit)让快慢差异显形;打稳态基线时再用 --timelimit。

想留证据就加 --csvfile /tmp/bench.csv,把每一轮两列数字都记下来,事后对比参数改动的效果。

动态追踪:bpftrace 一行流

当静态工具告诉你"盘的平均延迟是 15ms",但你需要知道是谁在发这些慢 I/O,就该上 bpftrace 了。

# 1. I/O 延迟分布直方图 —— 看清长尾
biolatency-bpfcc 10 1

# 2. 逐条打印 I/O,含进程名、扇区、延迟
biosnoop-bpfcc | head -40

# 3. 按进程统计 I/O 大小
bpftrace -e 'tracepoint:block:block_rq_issue { @[comm] = hist(args->bytes); }'

# 4. 抓超过 100ms 的慢 I/O 及其调用栈
bpftrace -e 'kprobe:blk_account_io_done /nsecs/ { ... }'

biolatency 的输出最能说明问题:

     usecs               : count     distribution
         0 -> 127        : 84213    |████████████████████████████████████████|
       128 -> 255        : 12043    |█████                                   |
       256 -> 511        : 3211     |█                                       |
    ...
     16384 -> 32767      : 89       |                                        |   ← 长尾在这

平均延迟可能只有 0.2ms,但这 89 个 16ms 以上的请求,就是业务侧感知到的卡顿来源。

!生产环境用追踪工具的纪律
  • 先在测试机上验证命令,不要在生产上现敲现试
  • 限定运行时长(timeout 30 bpftrace ...),不要开着就走
  • 高频事件(如每次内存分配)不要 trace,开销可能压垮机器
  • 记录你做了什么、什么时候做的,方便事后关联业务抖动

压测报告怎么写才有说服力

跑完数字只是原料。一份能用于决策的报告要包含:

## 测试环境
- 硬件:8 × Dell R750,每节点 12 × 7.68TB NVMe,2 × 25GbE
- 软件:Ceph 20.2.1,3 副本,pg_num=256
- 客户端:4 台,与存储节点独立

## 测试方法
- 工具:elbencho 3.1-7(4 台客户端 service 模式)
- 参数:见附录完整命令行
- 数据量:每线程 100 GiB(远超节点内存,避免只测到缓存与启动开销)
- 每组跑 300 秒,取稳态区间;每组重复 3 次取中位数

## 结果
| 场景 | 块大小 | 队列深度 | IOPS | 带宽(last done) | 平均延迟 | P99 延迟 | first/last 差距 |
| ... |

## 结论与瓶颈判断
- 顺序写在 11.2 GB/s 见顶,与 cluster 网理论上限吻合 → 瓶颈在网络
- 4K 随机写 IOPS 未达单盘能力之和的 50% → 需进一步排查

## 未覆盖的场景
- 未测试恢复期间的性能衰减
- 未测试多租户混合负载
✓最后一段最重要

"未覆盖的场景"这一段决定了报告的可信度。明确说出没测什么,比把测了的部分吹得天花乱坠更专业——因为看报告的人需要知道风险在哪。

Checkpoint单选

用 elbencho 测一块企业级 NVMe 的顺序写,跑了 30 秒得到 6.8 GB/s,远超厂商标称的 5 GB/s。最可能的原因是?

Checkpoint单选

iostat 显示某块盘平均延迟只有 0.3ms,但业务方反馈偶发卡顿。下一步最该做什么?

Checkpoint单选

elbencho 跑 12 块盘的顺序写,固定数据量、不加 --timelimit,结果 first done 4870 MiB/s、last done 2910 MiB/s。下一步最该做什么?

这节课的落点

  • 先用零开销的静态工具收敛范围,再用 bpftrace 类工具挖根因
  • elbencho 四件套:顺序读写测带宽、随机读写测 IOPS;--direct 必加,--timelimit 时长要够
  • 同一个 elbencho 加 --service / --hosts 就能多客户端压共享文件系统,尤其是多文件元数据场景
  • 结果表看两列:first done 是满载峰值,last done 是这次运行的聚合完成情况;差距超过 20% 先查不均衡(数据量太小、慢盘、切分不均、分布不均、后台任务),别急着记数字
  • 判断硬件性能上限、做验收对比理论值只用 first done;拿被拖尾稀释过的 last done 去比理论值,会误判成"硬件不达标"
  • 报告不能直接抄 last done:两列都写、注明数据量与时长、重复 3 次取中位数,结论给区间并解释差距来源
  • 裸盘压测顺便用 lspci 查 PCIe 是否 downgraded,这类硬件问题要在上线前发现
  • 报告必须写清方法、环境和未覆盖的场景

L0 到此结束。你已经具备了在任何一台 Linux 机器上定位性能问题的基本能力——接下来把这套能力用到分布式存储上。

延伸资料

  • Systems Performance, 2nd Edition — Brendan Gregg
  • k8s-in-actionstorage/elbencho/