尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

Linux NVMe 中断排查与性能优化:NUMA 实战 目录引言一、先建立正确的排查思路二、准备工具与安全注意事项安全注意事项三、识别 NVMe 控制器和 PCIe 地址1. 查看 NVMe 设备2. 获取 PCIe BDF 地址3. 确认上层设备是否经过其他块设备四、检查 PCIe 链路是否正常五、确认 MSI-X 是否启用1. 查看 MSI-X 能力2. 查看设备实际分配的 IRQ六、查看 NVMe 中断分布1. 查看 /proc/interrupts2. 只查看目标设备的 IRQ3. 动态观察中断计数七、检查 NVMe 队列数量和 blk-mq 映射1. 查看块层硬件队列2. 查看每个 blk-mq 队列对应的 CPU3. 查看控制器队列信息八、队列深度与中断合并调优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 中断集中在少数 CPUIRQ、应用线程和设备位于不同 NUMA 节点irqbalance与手工中断绑定策略冲突队列深度过大造成尾延迟升高中断频率过高消耗大量 CPUPCIe 链路降速或降宽控制器进入低功耗状态后产生唤醒延迟虚拟化环境中的 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 节点排查时建议遵循以下顺序确认测试对象和实际物理设备检查驱动、PCIe 链路和设备错误确认 MSI-X 是否启用检查实际分配的 IRQ 和 NVMe 队列在真实负载下观察中断分布检查 IRQ、应用线程、内存和设备的 NUMA 关系调整 CPU 亲和性或工作负载布局最后再考虑队列深度、中断合并、轮询和电源策略。不要一开始就关闭 IOMMU、停止irqbalance或修改大量内核参数。先找到瓶颈再进行单变量调整。二、准备工具与安全注意事项常用工具包括sudo apt install pciutils nvme-cli numactl sysstat fio不同发行版的软件包管理命令可能不同。本文示例使用以下设备名称CTRLnvme0 NSnvme0n1其中/dev/nvme0是 NVMe 控制器字符设备/dev/nvme0n1是 Namespace 块设备/dev/nvme0n1p1是对应分区。安全注意事项不要在生产盘上执行随机写测试。对裸设备执行fio --rwrandwrite会破坏数据。修改 IRQ 亲和性可能影响线上延迟。重新加载 NVMe 驱动会使设备短暂离线。原始设备的只读测试虽然不会写入数据但仍然会占用带宽、队列和控制器资源。每次只修改一个变量并保留修改前的基线数据。三、识别 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 地址CTRLnvme0 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-mapperdm-crypt软件 RAIDNVMe 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 Count65 Masked- Vector table: BAR0 offset... PBA: BAR0 offset...其中Enable表示 MSI-X 已启用Enable-表示设备具备 MSI-X 能力但当前未启用Count65表示 MSI-X Table 支持的最大表项数量Masked-表示没有全局屏蔽全部 MSI-X 向量。需要注意Count65表示设备最多支持的 MSI-X 表项数量不表示 Linux 当前实际分配了 65 个 IRQ。实际分配数量应通过 sysfs 或/proc/interrupts查看。2. 查看设备实际分配的 IRQPCI_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如果存在以下参数可能影响 MSIpcinomsi除非为了定位特定硬件问题否则不建议禁用 MSI/MSI-X。六、查看 NVMe 中断分布1. 查看/proc/interruptsgrep -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 Queuenvme0q1、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 队列对应的 CPUfor 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_queues4中断合并和轮询的取舍原则中断合并适合高吞吐、可容忍一定延迟的场景可以减少 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 未启用或内核启动参数包含 pcinomsi5查看设备实际分配的 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队列深度和中断合并参数与负载类型匹配队列深度过大导致尾延迟升高或轮询模式占用过多 CPU2. 总结推荐的优化顺序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 性能问题或者对某个命令的输出有疑问欢迎在评论区留言。我会根据大家的反馈继续补充更多实战案例和排查技巧。觉得本文有帮助的话不妨点个赞、收藏并分享给同样在做存储性能优化的朋友让更多人少走弯路。
返回列表