土法炼钢 · 系统与基础设施

【SPDK 用户态存储】排障坐标系:绑盘、空转、队列满、传输超时与多路径

文章导航

分类入口
storagelinux
标签入口
#spdk#troubleshooting#vfio#reactor#queue-full#nvme-of#multipath#v26.01

目录

「盘已经 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"]

默认顺序(可裁剪):

  1. 环境:设备归属与大页(第 3 篇)。
  2. 进程:RPC socket 是否连对、STARTUP/RUNTIME(第 12 篇)。
  3. iostat / 错误计数定层(第 13 篇)。
  4. 仍像远端问题 → 传输与路径轴(第 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,或换了采样窗口。数字变美不等于系统变快。


八、五轴检查清单(可剪进门禁)

  1. 环境:驱动归属、大页、PCI allow/block。
  2. 进程:正确 -r socket;rpc_get_methods 当前态。
  3. CPU:cpumask / NUMA;区分空转税与真卡死。
  4. bdev:iostat ops/error;模块栈深度。
  5. 传输:transport 与网络;本机 vs 远端。
  6. 路径:path iostat;避免聚合误判。
  7. 配置存活:重启后是否预期用 -c 恢复(第 12 篇)——「昨天热插过」不是证据。
  8. 观测纪律:是否有人 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)

系列内

上一篇:可观测与性能口径 · 系列目录 · 下一篇:对照内核路径

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。

2026-08-13 · storage / linux

SPDK / 用户态存储栈:从 Reactor 到 NVMe-oF

补齐站内 NVMe 协议与 O_DIRECT/io_uring 组合路径之上的用户态轮询存储层:reactor、绑盘、NVMe 驱动、bdev、NVMe-oF 与 vhost,并以排障与选型收束。


By .