news 2026/10/3 2:34:33

Linux NVMe 中断排查与性能优化:NUMA 实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux NVMe 中断排查与性能优化:NUMA 实战

目录

引言

一、先建立正确的排查思路

二、准备工具与安全注意事项

安全注意事项

三、识别 NVMe 控制器和 PCIe 地址

1. 查看 NVMe 设备

2. 获取 PCIe BDF 地址

3. 确认上层设备是否经过其他块设备

四、检查 PCIe 链路是否正常

五、确认 MSI-X 是否启用

1. 查看 MSI-X 能力

2. 查看设备实际分配的 IRQ

六、查看 NVMe 中断分布

1. 查看 /proc/interrupts

2. 只查看目标设备的 IRQ

3. 动态观察中断计数

七、检查 NVMe 队列数量和 blk-mq 映射

1. 查看块层硬件队列

2. 查看每个 blk-mq 队列对应的 CPU

3. 查看控制器队列信息

八、队列深度与中断合并调优

1. 查看当前队列深度

2. 使用 nvme set-feature 调整 NVMe 队列深度

3. 队列深度过大导致尾延迟升高的原理

4. 不同负载类型下建议的队列深度范围

5. 中断合并与轮询参数的查看与设置

九、总结与快速检查清单

1. 按排查顺序排列的检查项表格

2. 总结:推荐的优化顺序

3. 最常见的 NVMe 性能瓶颈误判场景及避免方法

十、互动与讨论


引言

NVMe SSD 通过 PCIe、DMA、多队列和 MSI-X 中断实现高并发、低延迟 I/O。但在实际系统中,即使 SSD 本身性能很高,也可能因为以下问题无法发挥应有性能:

  • MSI-X 没有正常启用;
  • I/O 队列或中断向量数量不足;
  • NVMe 中断集中在少数 CPU;
  • IRQ、应用线程和设备位于不同 NUMA 节点;
  • irqbalance与手工中断绑定策略冲突;
  • 队列深度过大,造成尾延迟升高;
  • 中断频率过高,消耗大量 CPU;
  • PCIe 链路降速或降宽;
  • 控制器进入低功耗状态后产生唤醒延迟;
  • 虚拟化环境中的 vCPU 调度或中断注入延迟。

本文从 Linux 实战角度介绍如何观察 NVMe 中断、分析 IRQ 与 CPU 的映射关系,并结合 NUMA、队列深度、中断合并和轮询模式进行优化。


一、先建立正确的排查思路

NVMe 性能问题不能只看 SSD 的带宽或 IOPS。一个完整的 I/O 路径包括:

应用线程 ↓ 文件系统或裸块设备 ↓ Linux 块层 blk-mq ↓ NVMe Submission Queue ↓ PCIe DMA ↓ NVMe 控制器 ↓ Completion Queue ↓ MSI-X 中断或轮询 ↓ CPU 完成请求

性能优化的目标是让以下资源合理对应:

应用线程 ↓ CPU ↓ blk-mq 硬件队列 ↓ NVMe Submission/Completion Queue ↓ MSI-X 向量 ↓ IRQ 处理 CPU ↓ NUMA 节点

排查时建议遵循以下顺序:

  1. 确认测试对象和实际物理设备;
  2. 检查驱动、PCIe 链路和设备错误;
  3. 确认 MSI-X 是否启用;
  4. 检查实际分配的 IRQ 和 NVMe 队列;
  5. 在真实负载下观察中断分布;
  6. 检查 IRQ、应用线程、内存和设备的 NUMA 关系;
  7. 调整 CPU 亲和性或工作负载布局;
  8. 最后再考虑队列深度、中断合并、轮询和电源策略。

不要一开始就关闭 IOMMU、停止irqbalance或修改大量内核参数。先找到瓶颈,再进行单变量调整。


二、准备工具与安全注意事项

常用工具包括:

sudo apt install pciutils nvme-cli numactl sysstat fio

不同发行版的软件包管理命令可能不同。

本文示例使用以下设备名称:

CTRL=nvme0 NS=nvme0n1

其中:

  • /dev/nvme0是 NVMe 控制器字符设备;
  • /dev/nvme0n1是 Namespace 块设备;
  • /dev/nvme0n1p1是对应分区。

安全注意事项

  1. 不要在生产盘上执行随机写测试。
  2. 对裸设备执行fio --rw=randwrite会破坏数据。
  3. 修改 IRQ 亲和性可能影响线上延迟。
  4. 重新加载 NVMe 驱动会使设备短暂离线。
  5. 原始设备的只读测试虽然不会写入数据,但仍然会占用带宽、队列和控制器资源。
  6. 每次只修改一个变量,并保留修改前的基线数据。

三、识别 NVMe 控制器和 PCIe 地址

1. 查看 NVMe 设备

nvme list

示例输出可能包括:

Node SN Model Namespace /dev/nvme0n1 XXXXXXXX Example NVMe SSD 1

查看控制器和 Namespace 关系:

nvme list-subsys

如果系统使用了 NVMe Multipath,一个 Namespace 可能通过多个控制器路径访问。此时必须确认 I/O 实际经过哪条路径。


2. 获取 PCIe BDF 地址

PCIe 设备通常使用以下格式标识:

Domain:Bus:Device.Function

例如:

0000:5e:00.0

可以通过 sysfs 获取控制器对应的 PCIe 地址:

CTRL=nvme0 BDF=$(basename "$(readlink -f /sys/class/nvme/$CTRL/device)") echo "$BDF"

然后查看设备和驱动:

lspci -s "$BDF" -nnk

正常情况下应看到:

Kernel driver in use: nvme

如果设备没有绑定nvme驱动,需要先检查:

  • 驱动是否加载;
  • 设备是否被 VFIO 接管;
  • 是否位于虚拟机中;
  • 内核日志中是否存在初始化错误。

3. 确认上层设备是否经过其他块设备

实际业务可能访问的是:

  • LVM 逻辑卷;
  • device-mapper;
  • dm-crypt;
  • 软件 RAID;
  • NVMe Multipath;
  • 容器中的映射设备;
  • 虚拟机中的虚拟磁盘。

可以查看块设备关系:

lsblk -o NAME,TYPE,PKNAME,MAJ:MIN,SIZE,FSTYPE,MOUNTPOINTS

如果业务访问的是/dev/mapper/...,不能简单假设所有 I/O 都落到某个固定 NVMe 控制器上。


四、检查 PCIe 链路是否正常

中断调优之前,应先确认 PCIe 链路没有降速、降宽或持续报错。

sudo lspci -s "$BDF" -vv

重点查看:

LnkCap: LnkSta: DevSta: MSI-X:

也可以筛选:

sudo lspci -s "$BDF" -vv | grep -E 'LnkCap:|LnkSta:|DevSta:|MSI-X:'

例如,设备能力可能是:

LnkCap: Speed 16GT/s, Width x4

实际链路状态可能是:

LnkSta: Speed 16GT/s, Width x4

如果LnkSta明显低于LnkCap,例如:

LnkCap: Speed 16GT/s, Width x4 LnkSta: Speed 8GT/s, Width x2

说明链路可能出现降速或降宽。常见原因包括:

  • 主板插槽只提供部分 PCIe 通道;
  • 转接卡或背板限制;
  • BIOS 配置问题;
  • PCIe 信号质量问题;
  • 设备安装位置不正确;
  • PCIe AER 错误后链路降级;
  • 虚拟化平台限制。

PCIe 链路问题不是 IRQ 绑定能够解决的。


五、确认 MSI-X 是否启用

1. 查看 MSI-X 能力

执行:

sudo lspci -s "$BDF" -vv | grep -A3 'MSI-X'

典型输出:

MSI-X: Enable+ Count=65 Masked- Vector table: BAR=0 offset=... PBA: BAR=0 offset=...

其中:

  • Enable+表示 MSI-X 已启用;
  • Enable-表示设备具备 MSI-X 能力,但当前未启用;
  • Count=65表示 MSI-X Table 支持的最大表项数量;
  • Masked-表示没有全局屏蔽全部 MSI-X 向量。

需要注意:

Count=65表示设备最多支持的 MSI-X 表项数量,不表示 Linux 当前实际分配了 65 个 IRQ。

实际分配数量应通过 sysfs 或/proc/interrupts查看。


2. 查看设备实际分配的 IRQ

PCI_PATH=/sys/bus/pci/devices/$BDF ls -1 "$PCI_PATH/msi_irqs"

输出可能是:

144 145 146 147 148

这些数字是 Linux IRQ 编号。

统计数量:

find "$PCI_PATH/msi_irqs" -mindepth 1 -maxdepth 1 | wc -l

如果msi_irqs目录不存在,应检查:

  • MSI/MSI-X 是否实际启用;
  • 设备是否由其他驱动管理;
  • 是否运行在虚拟机中;
  • 内核是否使用了禁用 MSI 的启动参数;
  • 驱动初始化是否失败。

检查启动参数:

cat /proc/cmdline

如果存在以下参数,可能影响 MSI:

pci=nomsi

除非为了定位特定硬件问题,否则不建议禁用 MSI/MSI-X。


六、查看 NVMe 中断分布

1. 查看/proc/interrupts

grep -E 'CPU|nvme' /proc/interrupts

示例:

CPU0 CPU1 CPU2 CPU3 144: 5 0 0 0 PCI-MSI nvme0q0 145: 0 10234 0 0 PCI-MSI nvme0q1 146: 0 0 11567 0 PCI-MSI nvme0q2 147: 0 0 0 10891 PCI-MSI nvme0q3

通常:

  • nvme0q0常用于 Admin Queue;
  • nvme0q1、nvme0q2等通常对应 I/O 队列;
  • 每一列表示该 IRQ 在对应逻辑 CPU 上累计处理的次数。

不同内核版本和驱动实现的命名可能不同,不能仅根据队列名称断定底层映射关系。


2. 只查看目标设备的 IRQ

为了避免系统中存在多个 NVMe 设备时产生混淆,可以根据 PCIe 设备的msi_irqs目录逐个查看:

for path in /sys/bus/pci/devices/$BDF/msi_irqs/*; do irq=${path##*/} awk -v n="$irq:" '$1 == n {print}' /proc/interrupts done

这样可以把 IRQ 与具体 PCIe 控制器对应起来。


3. 动态观察中断计数

watch -n 1 'grep -E "CPU|nvme0q" /proc/interrupts'

在执行 I/O 负载时,观察以下现象:

  • 哪些数据队列 IRQ 正在增长;
  • IRQ 是否只集中到一个 CPU;
  • 不同队列是否均匀增长;
  • Admin Queue 中断是否很少;
  • 中断数是否与负载变化大致一致。

不要期待每个 I/O 都产生一次中断。原因包括:

  • 一次中断可以批量处理多个 Completion Queue 条目;
  • 控制器可能使用中断合并;
  • 驱动可能在一次中断中回收多个完成请求;
  • 部分队列可能使用轮询;
  • 工作负载可能命中页缓存,没有产生真实块 I/O。

七、检查 NVMe 队列数量和 blk-mq 映射

1. 查看块层硬件队列

find /sys/block/$NS/mq \ -mindepth 1 -maxdepth 1 -type d

统计数量:

find /sys/block/$NS/mq \ -mindepth 1 -maxdepth 1 -type d | wc -l

这些目录表示 Linux blk-mq 为该块设备暴露的硬件上下文。它们与 NVMe I/O Queue 关系密切,但不能简单认为:

blk-mq 目录数量 = MSI-X 数量 = CPU 数量

三者可能不同。


2. 查看每个 blk-mq 队列对应的 CPU

for q in /sys/block/$NS/mq/*; do printf '%s: ' "$(basename "$q")" cat "$q/cpu_list" done

示例:

0: 0,4 1: 1,5 2: 2,6 3: 3,7

这表示不同 CPU 提交的块请求会被映射到对应的硬件队列。

如果一个应用只运行在 CPU 2,那么它可能主要使用 CPU 2 所映射的某个硬件队列。因此,单线程测试只激活一个 NVMe 队列通常是正常现象,不能直接判断为队列配置错误。


3. 查看控制器队列信息

部分内核会提供:

test -r /sys/class/nvme/$CTRL/queue_count &

八、队列深度与中断合并调优

1. 查看当前队列深度

块层为每个请求队列维护一个nr_requests参数,表示该队列最多可以容纳的待处理请求数量。查看当前值:

cat /sys/block/nvme0n1/queue/nr_requests

典型输出:

256

这个值表示块层允许排队等待下发的请求上限。它并不直接等于 NVMe 控制器内部的队列深度,但会影响请求在块层的堆积程度。

2. 使用 nvme set-feature 调整 NVMe 队列深度

NVMe 规范通过 Number of Queues 特性(Feature ID 0x07)配置 I/O 队列数量,而队列深度通常在控制器初始化时由驱动协商确定。对于支持动态调整的设备,可以使用nvme set-feature查看或修改相关特性:

sudo nvme get-feature /dev/nvme0 -f 0x07 -H

查看当前队列配置:

sudo nvme set-feature /dev/nvme0 -f 0x07 -v 32

上面的命令尝试将 I/O 队列数量设置为 32。需要注意:

  • 实际生效的队列数量由驱动、控制器能力和内核参数共同决定;
  • 修改后建议重新加载驱动或重启系统,使配置完整生效;
  • 生产环境修改前务必记录基线数据,并确认业务窗口允许短暂离线。

块层请求队列深度也可以通过 sysfs 调整:

echo 512 | sudo tee /sys/block/nvme0n1/queue/nr_requests

该修改立即生效,但重启后会恢复默认值。如果需要持久化,可以写入 udev 规则或系统启动脚本。

3. 队列深度过大导致尾延迟升高的原理

队列深度越大,控制器越容易保持忙碌,理论上可以提高吞吐量。但过大的队列深度会带来以下问题:

  • 请求在队列中等待的时间变长,单个请求的完成时间被拉长;
  • 多个请求同时竞争控制器资源,造成排队抖动;
  • 中断处理批量变大,CPU 处理完成事件的时间点更集中,延迟分布变宽;
  • 在高负载下,队头阻塞会放大尾延迟,使 P99 和 P99.9 明显恶化。

因此,队列深度并不是越大越好。对于延迟敏感型负载,较小的队列深度配合足够的队列数量,往往能获得更稳定的延迟表现。

4. 不同负载类型下建议的队列深度范围

负载类型典型场景建议队列深度说明
高 IOPS随机读写、数据库事务256 - 1024保持较高深度以充分利用多队列并行能力
高带宽顺序读写、日志写入128 - 512深度过高收益有限,反而增加延迟
低延迟在线交易、实时查询32 - 128控制排队长度,优先保证延迟稳定

以上范围是经验参考值,实际最优值需要通过压测对比确定。建议使用fio分别测试不同深度下的吞吐和延迟分布,再结合业务指标选择。

5. 中断合并与轮询参数的查看与设置

NVMe 驱动在部分内核版本中提供中断合并和轮询相关参数,通常位于控制器 sysfs 目录下。查看当前配置:

ls /sys/class/nvme/nvme0/queue/

查看是否支持轮询模式:

cat /sys/class/nvme/nvme0/queue/io_poll

输出0表示轮询未启用,1表示已启用。启用轮询:

echo 1 | sudo tee /sys/class/nvme/nvme0/queue/io_poll

部分驱动还支持中断合并相关参数,例如:

cat /sys/module/nvme/parameters/io_timeout cat /sys/module/nvme/parameters/poll_queues

其中:

  • io_timeout控制 I/O 超时时间,间接影响请求重试和延迟表现;
  • poll_queues指定使用轮询模式的队列数量,适合延迟极敏感且 CPU 资源充足的场景。

修改内核模块参数需要重新加载模块或写入启动参数:

sudo modprobe -r nvme sudo modprobe nvme poll_queues=4

中断合并和轮询的取舍原则:

  • 中断合并适合高吞吐、可容忍一定延迟的场景,可以减少 CPU 中断开销;
  • 轮询模式适合低延迟、高 CPU 占用可接受的场景,可以避免中断延迟抖动;
  • 两者都需要结合真实负载验证,不能仅凭理论判断。

九、总结与快速检查清单

1. 按排查顺序排列的检查项表格

下表汇总了本文涉及的完整排查流程,按建议的执行顺序排列。每项都给出检查命令、预期结果和常见问题,便于在遇到 NVMe 性能问题时快速对照。

排查顺序检查项检查命令预期结果常见问题
1识别 NVMe 控制器和 PCIe 地址nvme list、readlink -f /sys/class/nvme/nvme0/device能确认设备型号、Namespace 和 BDF 地址设备未绑定 nvme 驱动,或被 VFIO 接管
2确认上层设备是否经过其他块设备lsblk -o NAME,TYPE,PKNAME,MAJ:MIN能看清 LVM、dm-crypt、Multipath 等映射关系业务访问的是 /dev/mapper/...,误以为 I/O 直接落到固定控制器
3检查 PCIe 链路状态sudo lspci -s "$BDF" -vv | grep -E 'LnkCap:|LnkSta:'LnkSta 的速度和宽度不低于 LnkCap链路降速或降宽,IRQ 绑定无法解决
4确认 MSI-X 是否启用sudo lspci -s "$BDF" -vv | grep -A3 'MSI-X'显示 Enable+,且 msi_irqs 目录存在MSI-X 未启用,或内核启动参数包含 pci=nomsi
5查看设备实际分配的 IRQls -1 /sys/bus/pci/devices/$BDF/msi_irqs能看到多个 IRQ 编号,数量与队列数匹配msi_irqs 目录不存在,或 IRQ 数量明显偏少
6观察中断分布watch -n 1 'grep -E "CPU|nvme0q" /proc/interrupts'负载下各 I/O 队列 IRQ 分散到多个 CPU中断集中在单个 CPU,或队列间计数严重不均
7检查 blk-mq 队列与 CPU 映射for q in /sys/block/$NS/mq/*; do cat "$q/cpu_list"; done每个硬件队列对应合理的 CPU 集合单线程测试只激活一个队列,误判为配置错误
8确认 NUMA 关系cat /sys/bus/pci/devices/$BDF/numa_node、numactl --hardware设备、IRQ 处理 CPU 和应用线程位于同一 NUMA 节点跨 NUMA 访问导致延迟升高,IRQ 绑定方向错误
9调整 IRQ 亲和性echo "$cpu" | sudo tee /proc/irq/$irq/smp_affinity_list各队列 IRQ 均匀绑定到设备所在 NUMA 节点的 CPUirqbalance 与手工绑定冲突,调整后被覆盖
10调整队列深度与中断合并cat /sys/block/nvme0n1/queue/nr_requests、cat /sys/class/nvme/nvme0/queue/io_poll队列深度和中断合并参数与负载类型匹配队列深度过大导致尾延迟升高,或轮询模式占用过多 CPU

2. 总结:推荐的优化顺序

NVMe 性能优化必须遵循从底层到上层的排查顺序,避免在错误的方向上浪费精力。推荐的顺序是:先确认 PCIe 链路和 MSI-X 启用状态,再分析中断分布和 NUMA 关系,最后才调整队列深度和中断合并。

具体来说,PCIe 链路降速或降宽属于硬件层面的问题,任何 IRQ 绑定或队列参数调整都无法弥补,因此必须最先排除。MSI-X 未启用则意味着设备可能退回到传统中断模式,中断向量数量不足会直接限制多队列并行能力,这也是后续所有分析的前提。只有确认链路正常、MSI-X 已启用,中断分布和 NUMA 关系的分析才有意义。

在中断分布和 NUMA 关系层面,重点是把 IRQ 处理 CPU、应用线程和设备放在同一个 NUMA 节点,并让各队列的中断均匀分散到该节点的多个 CPU 上。这一步通常能解决大部分中断集中和跨 NUMA 访问导致的延迟问题。

最后才考虑队列深度和中断合并。队列深度并非越大越好,过大会放大尾延迟;中断合并和轮询模式各有适用场景,需要结合真实负载验证。按照这个顺序排查,可以避免在硬件或中断配置存在根本问题时,盲目调整上层参数而收效甚微。

3. 最常见的 NVMe 性能瓶颈误判场景及避免方法

  • 误判一:中断集中在单个 CPU 就认为是 IRQ 亲和性问题。单线程测试只激活一个 NVMe 队列是正常现象,因为应用只运行在某个 CPU 上,对应的请求只会映射到该 CPU 关联的硬件队列。避免方法:先确认负载是否多线程、多队列,再观察中断分布;如果单线程负载下中断集中,不能直接判定为配置错误。
  • 误判二:IOPS 不达标就立刻调整队列深度。队列深度只是影响性能的因素之一,PCIe 链路降速、MSI-X 未启用或跨 NUMA 访问都可能造成同样的现象。避免方法:先按检查清单逐项排除硬件和中断配置问题,再考虑队列深度调整,避免在错误方向上反复试错。
  • 误判三:看到 P99 延迟升高就认为是中断合并导致的。尾延迟升高可能来自队列深度过大、队头阻塞、控制器低功耗唤醒或应用线程跨 NUMA 访问。避免方法:先观察中断分布和 NUMA 关系,确认没有更底层的问题后,再评估中断合并参数的影响。
  • 误判四:业务访问 /dev/mapper/... 就假设 I/O 直接落到某个 NVMe 控制器。LVM、dm-crypt、Multipath 等映射设备可能把 I/O 分散到多个底层设备,或经过额外的软件层。避免方法:先用 lsblk 确认块设备关系,再针对实际物理设备进行排查。
  • 误判五:修改 IRQ 亲和性后立即测试,发现没有效果就认为方法无效。irqbalance 可能在后台覆盖手工绑定,或者应用线程仍然运行在错误的 NUMA 节点上。避免方法:先停止并禁用 irqbalance,同时把应用线程绑定到设备所在 NUMA 节点,再重新压测验证。

十、互动与讨论

读完本文,相信你已经对 Linux NVMe 中断排查与 NUMA 优化有了系统的认识。为了帮助你把知识真正落地,这里留几个互动问题,欢迎在评论区一起交流:

  • 你遇到过中断集中在单个 CPU 的情况吗?当时是如何定位和解决的?是 IRQ 亲和性问题,还是单线程负载的正常现象?
  • 你的生产环境队列深度设置是多少?是否针对高 IOPS、高带宽或低延迟负载做过针对性调优?调优前后的 P99 延迟变化如何?
  • 你更倾向中断合并还是轮询模式?在什么业务场景下你会选择切换,切换后 CPU 占用和延迟表现有什么变化?
  • 有没有踩过 NUMA 相关的坑?比如应用线程、IRQ 和设备跨节点访问导致性能骤降,你是如何发现并解决的?

如果你在实战中遇到了本文没有覆盖到的 NVMe 性能问题,或者对某个命令的输出有疑问,欢迎在评论区留言。我会根据大家的反馈,继续补充更多实战案例和排查技巧。

觉得本文有帮助的话,不妨点个赞、收藏并分享给同样在做存储性能优化的朋友,让更多人少走弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 2:33:50

[程序人生]人生必须要不停的上班吗?

💡 我以前采访过一个做了三十多年铁路调度的老师傅。他快退休的时候,我问了一个很俗的问题:"终于不用上班了,最想干什么?"我以为他会说旅游、睡觉、钓鱼,结果他想了半天,说&#xff1…

作者头像 李华
网站建设 2026/10/3 2:32:59

车借别人开回来?按这套流程查一遍,不吃亏也不伤和气!

相信不少车主都有过这种“社恐时刻”:亲戚、朋友或者同事张口借车,拒绝吧怕人家说你小气,以后见面都尴尬;钥匙递出去的瞬间,心里就开始打鼓:他开车猛不猛?会不会过减速带不刹车?有没…

作者头像 李华