服务器磁盘队列深度是什么意思?存储延迟与IO性能分析

在排查存储性能时,你大概见过这样的困惑:两块同样标称的 SSD,一块跑得飞快,另一块延迟却高得离谱;或者监控上磁盘 %util 只有 40%,但业务已经卡得受不了。这些现象背后都指向同一个容易被忽略的概念——队列深度(Queue Depth)。它决定了存储设备能同时处理多少个 IO 请求,也直接决定了在高并发下存储的真实表现。

这篇文章要讲清楚队列深度的本质:它是怎么产生的、机械盘和 SSD 为什么对它反应完全不同、Linux 里怎么看队列深度、IO 调度器怎么影响它,以及怎么通过压测找到设备的"甜蜜点"。读完你应该能解释清楚:为什么别人的 SSD 能跑出几万 IOPS,而你的只能跑出几千。

先给一个总的判断框架:磁盘性能由三个维度决定——IOPS(每秒能处理多少请求)、吞吐量(每秒能传输多少数据)、延迟(一个请求要多久返回)。队列深度是连接这三者的杠杆。队列浅,延迟低但 IOPS 上不去;队列深,IOPS 高但延迟会涨。运维要做的不是无脑把队列调大,而是找到业务可接受延迟下的最优队列深度。

一、队列深度到底是什么

队列深度(Queue Depth,QD)指的是存储设备在同一时刻能排队等待处理的 IO 请求数量。可以把磁盘想象成一个服务窗口:队列深度就是允许在窗口外排队的人数上限。

在传统机械硬盘(HDD)时代,这个窗口一次只能服务一个人(磁头同一时刻只能在一个位置读写),所以队列深度对 HDD 基本没用——排队再长,磁头也得一个一个来,这就是为什么 HDD 的随机 IOPS 只有一百多的量级(7200 转机械盘经验值)。

SSD 则完全不同。SSD 内部有多个闪存通道(channel)和多个 die,可以并行处理多个请求。队列深度越深,越能把这些并行能力用满。所以同样一块盘,QD1 下可能只有几千 IOPS,QD32 下能到几万甚至几十万。这就是"队列深度"对 SSD 至关重要的原因。

Linux 的块设备层对每个设备有一个请求队列,队列里的请求有几种状态:等待合并、等待调度、已下发到设备。我们说的"队列深度"通常指设备层面能同时持有的未完成请求数,体现为 /sys/block//queue/nr_requests(调度器队列长度)和更底层的 queue_depth。

二、关键指标与它们的关系

理解下面这几个指标的关系,是读懂存储性能的前提:

指标 含义 单位 观察位置
IOPS 每秒 IO 请求数 次/秒 iostat 的 r/s + w/s
吞吐量 每秒传输字节数 MB/s iostat 的 rkB/s + wkB/s
平均延迟 await 单个 IO 平均耗时 毫秒 iostat -x 的 await
平均队列长度 aqu-sz 未完成请求的平均值 个 iostat -x 的 aqu-sz
%util 设备忙碌时间占比 % iostat -x 的 %util
队列深度 QD 最大并发请求数 个 设备参数设定

它们之间有一个近似关系:IOPS ≈ 队列深度 ÷ 平均延迟。举例来说,如果平均延迟是 1 毫秒,队列深度是 32,理论峰值 IOPS 大约就是 32000。反过来,延迟是固定的物理属性(取决于设备),要达到高 IOPS 就必须加深队列。这条公式是理解存储性能的核心。

还有一个常见误区:SSD 的 %util 经常虚高。因为 SSD 的 %util 计算方式基于"设备是否有请求在处理",哪怕它并行处理能力很强,只要队列里有请求,%util 就可能显示 100%。所以对 SSD,%util=100% 不等于"饱和到顶",更该看的是 await 有没有上升、aqu-sz 有多深。

三、查看和调整队列深度

# 查看设备的队列参数
cat /sys/block/sda/queue/nr_requests      # 调度器队列长度
cat /sys/block/nvme0n1/queue/nr_requests
cat /sys/block/sda/device/queue_depth     # 设备队列深度(部分设备可见)

# 查看当前调度器
cat /sys/block/sda/queue/scheduler
# 输出示例:[mq-deadline] kyber bfq none

# 调整调度器队列长度(临时)
echo 512 > /sys/block/sda/queue/nr_requests
# 用 iostat 观察队列表现,重点是 aqu-sz 和 await
iostat -x 1 5
# Device  r/s    w/s   rkB/s   wkB/s  await  r_await w_await aqu-sz %util
# sda   8000   2000  64000   16000    2.5     2.2     3.1   12.6  98.0
# aqu-sz 平均 12.6 说明队列不深,await 2.5ms 是主要延迟

# 需要 sysstat:
# RHEL/CentOS/Rocky
dnf install -y sysstat
# Debian/Ubuntu
apt install -y sysstat

针对 NVMe SSD,通常建议把调度器设为 none(内核自带多队列,绕过软件调度),并保持较高的 nr_requests:

# 查看 NVMe 支持的调度器
cat /sys/block/nvme0n1/queue/scheduler
# 临时设置为 none
echo none > /sys/block/nvme0n1/queue/scheduler

# 永久生效(推荐用 udev 规则,避免重启失效)
# 创建 /etc/udev/rules.d/60-iosched.rules,内容示例:
# ACTION=="add|change", KERNEL=="nvme*", ATTR{queue/scheduler}="none"
# 然后执行:
udevadm control --reload-rules
udevadm trigger

风险提示:修改调度器和队列长度会影响该设备上的所有 IO。对延迟敏感的业务(数据库),改错可能导致延迟飙升。远程操作前记录原值,回滚方式是写回原调度器并 udevadm trigger。建议先在测试环境验证。

四、压测:找到设备的"甜蜜点"

要用满存储能力,得先知道它的极限在哪。压测工具能直接指定队列深度。

# fio 是最常用的存储压测工具
# 安装:RHEL/CentOS/Rocky: dnf install -y fio
#       Debian/Ubuntu: apt install -y fio

# 4K 随机读,队列深度 1(贴近单线程顺序访问)
fio --name=randread --ioengine=libaio --iodepth=1 \
    --rw=randread --bs=4k --direct=1 --size=1G \
    --numjobs=1 --runtime=30 --group_reporting --filename=/dev/sdb

# 4K 随机读,队列深度 32(高并发)
fio --name=randread32 --ioengine=libaio --iodepth=32 \
    --rw=randread --bs=4k --direct=1 --size=1G \
    --numjobs=4 --runtime=30 --group_reporting --filename=/dev/sdb

观察两次压测结果的差异,尤其是 IOPS 和延迟。通常你会看到:QD 从 1 提到 32,IOPS 大幅提升,但延迟也从零点几毫秒涨到几毫秒。这个"延迟还能接受"的队列深度,就是业务应该用的甜蜜点。

风险提示:fio 加 --direct=1 和 --filename=/dev/sdb 会直接写入裸设备,会覆盖数据!测试务必用没有数据的测试盘或临时文件(把 filename 换成 /mnt/test/fiofile 形式),千万不要对着生产数据盘执行写测试。回滚无从谈起——数据一旦覆盖就没了。

应用侧如果要提高并发,可以通过多线程/多队列发起 IO(如异步 IO、多线程读文件),本质都是加深队列深度。

五、延迟、IOPS、队列深度的取舍场景

业务场景 推荐队列深度 优先级 理由
数据库 OLTP 中等(16-64) 延迟优先 事务对延迟敏感,但不能太浅否则吞吐不够
大数据批处理 深(128+) 吞吐优先 能容忍较高延迟,追求 IOPS 和带宽
Web 静态文件 浅到中 均衡 请求量不稳定,兼顾响应
日志顺序写 浅即可 吞吐优先 顺序写本身效率高,队列深度意义小
虚拟机宿主 深 吞吐优先 多虚机并发,需要吞吐支撑

这里给的是量级参考(经验值),具体要压测确定。一个稳妥的思路是:先用 fio 测出设备在不同 QD 下的延迟曲线,找到延迟开始陡增的拐点,把队列深度控制在拐点之前。延迟曲线通常是先平缓后陡峭,一旦进入陡峭段,再加队列深度只会让延迟爆炸而 IOPS 提升有限。

常见问题

Q:%util 到 100% 是不是就说明磁盘到瓶颈了?
对 HDD 基本是;对 SSD 不一定。SSD 的 %util 计算方式容易虚高,要看 await 是否明显上升、aqu-sz 是否在增长。%util=100% 但 await 稳定,说明设备还有余力。

Q:SSD 要不要调大队列深度?
要,但要看场景。NVMe SSD 建议用 none 调度器并保持较深队列,让设备自身并行能力发挥作用。随机读密集型的服务收益明显,顺序写为主的场景收益有限。

Q:为什么我调大了 nr_requests,性能没变化?
nr_requests 是调度器层的队列长度,不等于设备能并行处理的深度。如果设备本身队列浅,或者应用只能串行发 IO,调大它没用。真正的瓶颈可能在应用的并发模型。

Q:aqu-sz(平均队列长度)多少算正常?
没有固定值。aqu-sz 接近 1 说明基本没有排队,设备没被用满;aqu-sz 很高但没有对应的 IOPS 提升,说明设备已经饱和、请求在堆。要结合 await 判断。

Q:机械盘还有必要优化队列深度吗?
意义不大。HDD 的随机 IOPS 受限于物理寻道,队列加深基本不会提升随机 IOPS,只会堆延迟。HDD 场景更该做的是减少随机 IO(顺序化、加缓存、上 SSD 做缓存层)。

总结

队列深度是存储能同时处理的并发请求数,它决定了在高并发下能压出多少 IOPS。核心关系是 IOPS ≈ 队列深度 ÷ 延迟,延迟是设备物理属性,要提高 IOPS 就得加深队列。

HDD 对队列深度不敏感(受寻道限制),SSD/NVMe 对队列深度非常敏感,这就是两者性能差异的关键之一。

排查工具组合:iostat -x 看 aqu-sz、await、%util;/sys/block/*/queue/ 看和改调度器与队列参数;fio 压测找甜蜜点。

调优要点:NVMe 用 none 调度器,队列深度按业务延迟容忍度定,延迟曲线拐点之前是安全区。

压测和调参都有风险,fio 直接写裸设备会毁数据,改调度器影响全局 IO,务必用测试环境、记录原值、留好回滚。