「盘已经 setup 了」与「业务仍超时 / CPU 空转 / 队列拒绝」可以同时为真。排障若从品牌口号或一张无口径 Grafana 起步,会在内核路径、SPDK 进程与远端 initiator 之间空转;若从固定坐标系起步,多数故障落进有限几格。
本文是系列第 14 篇:按 五轴 映射症状,并给出核对顺序。清单是工程方法,不是厂商 runbook 全文;不粘贴未执行命令输出。
本文是「SPDK / 用户态存储栈」系列第 14 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 13 篇 · 可观测与性能口径 iostat / 官方 report 第 14 篇 · 排障坐标系 症状 → 五轴 → 核对 第 15 篇 · 对照内核路径 SPDK vs 内核 vs io_uring
版本锚定:SPDK v26.01;轴定义对齐本系列 index 五个关键问题与 01–10 机制篇。
一、先选轴,再动命令
环境绑盘轴: VFIO/UIO、大页、设备是否仍被内核占用?
Reactor CPU 轴: poller 是否空转占满?是否有人在 reactor 上阻塞?
bdev 队列轴: 提交被拒、队列满、模块叠层是否过深?
传输超时轴: NVMe-oF / 网络 / RDMA·TCP 模式与 keepalive?
多路径轴: 单路径失败是否被聚合成「整盘死」?
flowchart TD
symptom["Symptom: bind fail / spin / stall / timeout"] --> axis["Pick axis"]
axis --> env["Env / bind"]
axis --> cpu["Reactor / CPU"]
axis --> q["bdev queue"]
axis --> xport["Transport timeout"]
axis --> path["Multipath"]
env --> setup["setup.sh / driver / hugepage"]
cpu --> reactors["framework_get_reactors / cpumask"]
q --> iostat["bdev_get_iostat / errors"]
xport --> nvmf["listener / transport stats"]
path --> pathio["bdev_nvme_get_path_iostat"]
默认顺序(可裁剪):
- 环境:设备归属与大页(第 3 篇)。
- 进程:RPC socket 是否连对、STARTUP/RUNTIME(第 12 篇)。
- iostat / 错误计数定层(第 13 篇)。
- 仍像远端问题 → 传输与路径轴(第 8–10 篇)。
二、环境绑盘轴:起不来或「消失的盘」
症状:setup.sh 失败、attach
controller 失败、重启后设备回到内核 nvme
驱动、大页不足导致启动失败。
| 核对 | 看什么 | 常见根因 |
|---|---|---|
| 驱动归属 | lspci -k / sysfs driver |
未绑 vfio-pci,或与内核驱动争用 |
| IOMMU / 权限 | dmesg、用户组、容器设备策略 | VFIO 无 IOMMU、设备未传入容器 |
| 大页 | nr_hugepages、-s 内存参数 |
HUGEMEM 不足或 mount 点不对 |
| PCI 过滤 | -B / -A |
白名单漏盘或黑名单误杀 |
| 回退 | 卸 SPDK 后是否恢复内核驱动 | 运维脚本只 bind 不 unbind |
排除树:PCI 看见 → 驱动是预期用户态栈 → 大页够 → RPC attach 成功。任一步断裂,不要先调 bdev QoS。
三、Reactor CPU 轴:空转与卡住
症状:核占用长期近 100% 但 IOPS 上不去;或延迟尖刺伴随「管理 RPC 也慢」。
轮询模型下,高 CPU 是默认态,不是 Linux 上「idle 为 0 即故障」的语义(第 2 篇)。要区分:
| 现象 | 解释方向 |
|---|---|
| 专用核满且吞吐符合预期 | 正常轮询税;问的是选型是否付得起(第 15–16 篇) |
| 核满且吞吐近 0 | 可能在空转等完成、qpair 未建好、或错误路径狂重试 |
| 核不满但延迟爆炸 | 可能 cpumask 不够、跨 NUMA、或阻塞打进 reactor |
| RPC 与数据面一起僵 | 查是否有人在错误线程上下文做重活 / 锁 |
核对:cpumask 是否与网卡/NVMe 同 NUMA;vhost
场景是否按 第 11 篇
把 controller 钉在 VM 同 socket;-m
是否过窄却开过多 queue。
四、bdev 队列轴:满、拒、错
症状:应用或 nvmf 侧报忙、I/O 失败计数升、叠了 raid/lvol 后延迟恶化。
| 核对 | 看什么 |
|---|---|
| 错误与 ops | bdev_get_iostat 的 error 与 ops
是否匹配流量预期 |
| 模块栈 | 是否不必要地经 aio/raid/lvol(第 7 篇) |
| 通道与队列 | per_channel、下层 NVMe qpair 深度 |
| reset 误用 | 是否有监控清零导致「突然归零」的假恢复 |
队列满是背压信号,不是「再加大页」能消的。先问提交速率是否超过完成速率,再问是否缺核或多了无用叠层。
五、传输超时轴:NVMe-oF 与网络
症状:initiator 超时、reconnect 风暴、仅远端路径失败而本机 bdev iostat 仍健康。
| 核对 | 看什么 |
|---|---|
| listener / subsystem | NQN、地址、端口、transport 类型是否与对端一致 |
| TCP vs RDMA | 拥塞、RNIC、中断模式(26.01 RDMA interrupt 边界见 第 9 篇) |
| 主机网络 | MTU、丢包、CPU 亲和与 RNIC 中断 |
| 对端 | 内核 nvme-rdma/nvme-tcp 与
SPDK host 行为差(第 10
篇) |
本机 bdev 正常 + 远端超时 → 先停在传输轴,不要重装 NVMe 固件。
六、多路径轴:局部失败被说成全局
症状:「盘挂了」,但可能是单路径 down;或 failback 后延迟分布变怪。
| 核对 | 看什么 |
|---|---|
| 路径级统计 | bdev_nvme_get_path_iostat |
| 聚合 iostat | 是否把活路径与死路径平均成「半死」 |
| 策略 | 故障切换是否可观测、是否与应用超时耦合 |
开放问题(系列级):多路径切换的可观测信号是否足够区分「路径抖动」与「介质故障」——终章继续列,本篇只要求排障时分路径看数。
七、串联案例(机制推演,非实录)
下列「案例」只演示选轴顺序,不冒充事故报告,不含伪造日志。
案例 A:业务超时,本机 nvme list
已空。
先环境轴:盘是否被 setup 到 vfio?若在 SPDK
上,内核工具空是预期,不是盘坏。再 RPC:bdev
列表是否还有名?若无,查是否重启且未 -c
加载(第 12 篇)。若有,查 iostat
是否还有完成;有完成则问题在上层超时预算,不在介质。
案例 B:CPU 100%,IOPS 近 0。
先 CPU 轴:是否刚 attach 失败却仍在 poll 空队列?是否传输
reconnect 狂刷?再看 error
计数是否攀升。不要先加核——空转加核只会买更贵的电费。
案例 C:远端 initiator 报 timeout,target 主机
bdev 很忙且无 error。
先进传输轴与网络,而不是换 NVMe。核对 listener
地址、防火墙、RDMA 链路;用 path iostat 看是否单路径。
案例 D:vhost guest 看盘异常或 I/O
错。
先确认 scsi 还是 blk;是否下层 bdev 被删;Windows
是否踩扇区已知问题(第 11 篇)。同时查 socket 是否连到别的
SPDK 实例。
案例
E:发布后「延迟变好」但业务无变更。
先观测纪律:是否有人 reset 了共享
iostat,或换了采样窗口。数字变美不等于系统变快。
八、五轴检查清单(可剪进门禁)
- 环境:驱动归属、大页、PCI allow/block。
- 进程:正确
-rsocket;rpc_get_methods当前态。 - CPU:cpumask / NUMA;区分空转税与真卡死。
- bdev:iostat ops/error;模块栈深度。
- 传输:transport 与网络;本机 vs 远端。
- 路径:path iostat;避免聚合误判。
- 配置存活:重启后是否预期用
-c恢复(第 12 篇)——「昨天热插过」不是证据。 - 观测纪律:是否有人 reset 了共享 iostat;告警窗口是否对齐。
写给 on-call:每排除一轴,就在工单里记「已否证某某」,避免下一班重复同一检查。五轴不是教条流程图——若症状明确是 chardev 连不上,可以直接从 vhost/socket 查,但仍应在结论里标明落点,方便以后归类。
与选型的衔接:若五轴排障长期卡在「付不起核税」或「配置库存无人维护」,那是第 16 篇排除树的输入,而不是再调一次队列深度能消掉的问题。
组织层再补一句:把本清单剪进发布门禁时,至少要有「谁负责 RPC 库存」与「谁有权 reset 统计」两个角色;否则第 12–13 篇写清的机制会在人肉流程里重新搅浑。
五轴方法的价值不在写出更长的命令列表,而在强制「一次只证伪一轴」。并行瞎改 cpumask、MTU、队列深度,会让事后无法知道哪一步有效。变更应像实验:假设(落在某轴)→ 单一操作 → 预期信号 → 记录。若信号不符合预期,先回到选轴,而不是叠下一项调优。对间歇性故障,还要固定采样窗口与负载发生器,否则「偶尔超时」无法稳定复现到传输轴或路径轴。
与安全运维的交叉点:RPC socket 暴露面过大时,误操作可以直接 detach 生产盘,症状会同时击中环境轴与配置轴。排障清单应包含「变更是否来自未登记 RPC」的快速检查,例如对照审计日志与对象图版本。把配置面与数据面权限分离写进同一坐标系,能避免把人为变更误诊成硬件抖动。
培训新人时,可用「故意制造」的受控故障做演练:错误的
-r、抽掉大页、拔掉单条路径、在 RUNTIME 误调
STARTUP
参数。每项演练只激活一轴,并要求写出否证过程。演练通过的标准不是「最终修好」,而是「第一次选轴正确且未污染其他旋钮」。这种能力比背
RPC 帮助表更能降低平均修复时间。
跨团队协作时,五轴还能当分工语言:平台组认环境和进程,存储数据面认 bdev 与 CPU,网络组认传输,虚拟化组认 vhost socket。缺少共同语言时,每个组都会证明「我的子系统是绿的」,却无人拥有端到端结论。把工单标题写成「轴:传输 / 假设:listener 地址漂移」这类格式,是便宜且有效的协同改进。
对于「只有部分客户端失败」的症状,先问是否同源:同一 initiator 镜像、同一 transport、同一 NQN、同一网络路径。不同源时,优先复制最小失败客户端做对照实验,而不是在 target 上全局改参。目标端全局旋钮能修好单一客户端时,常常只是掩盖了客户端配置漂移,下次换客户端还会回来。
文档与源码版本也要进清单:排障时确认进程是
v26.01 还是混进了非 LTS
二进制。特性开关与默认值漂移会导致「同一 runbook
在两台机器上行为不同」。用 rpc.py
或版本查询类接口(以当前版本文档为准)把版本写进工单头部,能减少跨版本误套手册。
当五轴都暂时「看起来正常」而业务仍失败时,把范围扩到客户端超时预算、DNS/负载均衡是否打到旧 listener、以及是否存在第二套并行部署。这不是放弃坐标系,而是承认端到端路径上还有本系列边界之外的跳——先点名那一跳,再决定是外链其它手册还是扩轴。
检查清单落地时,可把「否证记录」做成短表:时间、轴、假设、操作、信号、结论。表格比长聊天记录更能支持交接。若同一轴连续两次否证失败,应升级为双人复盘,而不是继续单人盲试。
否证记录保存九十天,足够覆盖多数版本回归窗口。
九、参考资料
官方文档(A)
- Getting Started / env 与
setup.sh说明;JSON-RPC 查询类 method;NVMe over Fabrics 与 NVMe Driver。 - Changelog v26.01:传输与 iostat 相关条目。
系列内
→ 上一篇:可观测与性能口径 · 系列目录 · 下一篇:对照内核路径
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【SPDK 用户态存储】用户态存储全景:从 Reactor 到 NVMe-oF 的坐标系
定位 SPDK 相对内核块层、O_DIRECT+io_uring 与 NVMe 协议百科的生态位;钉住 I/O 路径、线程模型、设备独占、块抽象、远端传输五条坐标系,并给出 16 篇阅读路线。
【SPDK 用户态存储】Initiator 与互通:SPDK host、内核 nvme-rdma/tcp 与多路径边界
对照 SPDK NVMe-oF host 路径与 Linux 内核 nvme-rdma/nvme-tcp initiator;厘清互通边界、bdev_nvme 附着方式,并在无伪造数字前提下说明多路径与 ANA 的谨慎用法。
SPDK / 用户态存储栈:从 Reactor 到 NVMe-oF
补齐站内 NVMe 协议与 O_DIRECT/io_uring 组合路径之上的用户态轮询存储层:reactor、绑盘、NVMe 驱动、bdev、NVMe-oF 与 vhost,并以排障与选型收束。
【SPDK 用户态存储】Reactor 与线程模型:Event、Poller 与 Message Passing
钉住 SPDK event framework:每核 reactor、事件队列、poller 轮询与跨核消息;说明 shared-nothing 边界,以及在 reactor 上阻塞等于饿死同核所有工作。