上一篇 下一篇 分享链接 返回 返回顶部

HDD升级NVMe 4.0后仍卡顿?服务器磁盘I/O瓶颈如何逐层定位

发布人:Minchunlin 发布时间:2026-10-04 20:22 阅读量:3

把HDD升级为NVMe 4.0后仍然卡顿,并不意味着新磁盘没有发挥性能,也不能仅凭“磁盘利用率100%”就断定是磁盘瓶颈。请求变慢可能同时受到CPU调度、内存回收、文件系统、I/O队列、网络等待、应用线程池或数据库锁等待影响。正确方法是固定同一时间窗口,观察响应时间、错误率、CPU、内存、磁盘队列和网络状态的联动变化。

引言配图

开始检查前,准备一个可回滚的维护窗口,确认服务器操作系统、业务进程、数据库实例和数据盘挂载点。至少保留最近一次配置与数据库备份,不要直接对生产裸设备运行写入型压测。以下命令以常见Linux服务器为例,iostat、pidstat、mpstat和sar通常来自sysstat工具包;如果命令不存在,应先核对发行版和软件包,不要直接套用不匹配的安装命令。

1. 建立观察窗口:先把“卡顿”变成可对照的数据

单个指标很容易误导。例如,%util较高不一定代表NVMe已经饱和,iowait较高也不一定是磁盘故障,内存使用率较高更不能直接等同于内存不足。需要在同一个时间段记录业务表现和系统指标。

建议选择业务确实出现卡顿的30至60秒,同时记录:

  • 请求或任务的平均响应时间、P95、P99;
  • 超时数、错误率、重试数;
  • CPU使用率、运行队列、I/O等待、内存压力;
  • 磁盘读写延迟、队列深度、读写量;
  • TCP重传、连接队列、应用工作线程或任务队列;
  • 数据库活动会话、锁等待、日志刷盘和慢查询数量。

先执行只读检查,确认设备、文件系统和挂载关系:

date
uname -a
nproc
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,ROTA,MODEL
df -hT
df -ih
findmnt -t ext4,xfs,btrfs
free -h

在复现卡顿的时间段采样:

vmstat 1 10
iostat -xz 1 10
pidstat -dur 1 10
mpstat -P ALL 1 10
ss -s

如果系统已安装sysstat,还可以补充网络和TCP数据:

sar -n DEV,TCP,ETCP 1 10

vmstat中的r表示等待运行的任务数量,b表示不可中断睡眠任务;wa表示CPU等待I/O的时间比例,si和so分别表示从交换区换入和换出。iostat -xz中的await包含排队与设备处理时间,aqu-sz反映平均队列长度,%util则表示设备处于处理状态的时间比例。

可以用下面的关联表做第一轮筛选:

同一时间窗口内的表现更可能的方向还需要排除的解释
P95上升,r_await/w_await和aqu-sz同步升高,业务进程处于阻塞状态存储路径或I/O队列数据库锁、文件系统错误、单个进程突发写入
运行队列r持续接近或超过CPU逻辑核数,us或sy升高,wa不高CPU或线程调度CPU降频、虚拟化steal、进程死循环
si/so持续增长,内存压力升高,响应时间随换入换出恶化内存不足或cgroup限制文件缓存回收、单个进程内存泄漏
磁盘指标平稳,但TCP重传、Send-Q或Recv-Q上升网络或上游服务应用线程池阻塞、连接池耗尽
系统I/O不高,但某接口P99和应用队列持续升高应用线程、锁或下游调用数据库锁等待、外部服务响应慢
数据库锁等待、日志刷盘延迟或检查点压力与请求延迟同时上升数据库内部瓶颈数据盘延迟、连接池配置、慢查询

这张表只用于缩小范围,不能替代后面的联动验证。

2. 检查CPU与调度:确认是不是“看起来像磁盘慢”

磁盘请求通常由进程发起。如果CPU已经耗尽,或者大量线程在调度队列中排队,即使NVMe本身延迟很低,应用仍然会表现为卡顿。

2.1 观察运行队列和CPU时间分布

重点关注以下组合:

  • r持续高于CPU逻辑核数,且us或sy明显升高:更接近计算或内核处理瓶颈;
  • wa高,但r不高,b和磁盘队列同时增加:更接近I/O等待;
  • st持续升高:虚拟机可能受到宿主机CPU争用影响;
  • 单个CPU接近100%,其他CPU较空闲:可能是单线程任务、锁或CPU亲和性问题,而不是总CPU不足。

可以按进程查看CPU和I/O行为:

pidstat -u -d -p ALL 1 10

如果某个进程的CPU占用很高,同时磁盘读写并不明显,应优先检查该进程的计算逻辑、压缩、序列化、日志格式化或频繁系统调用,而不是继续升级磁盘。

2.2 优化与验证

CPU方向的调整应从减少无效工作开始:

  • 降低不必要的调试日志和重复计算;
  • 检查工作线程数是否过多,避免线程争抢CPU后反而增加上下文切换;
  • 将批处理、压缩、索引构建安排到低峰期;
  • 确认虚拟机是否存在CPU steal;
  • 对单线程程序,先确认应用本身是否支持并行,不要只增加进程数量。

成功验证的标准不是“CPU使用率越低越好”,而是同等请求量下,运行队列下降,P95/P99改善,错误率不升高,且磁盘延迟没有因为新增并发而恶化。

如果增加工作线程后延迟和上下文切换反而升高,应立即恢复原线程数。线程数、进程数或CPU亲和性配置必须保留旧值,采用单次变更、短时间复测的方式回滚。

3. 检查内存与交换:避免缓存回收被误判为NVMe故障

升级NVMe后,系统可能因为更快的设备而承受更高并发,但内存仍然不足。此时内核频繁回收文件缓存或使用交换区,应用看到的就是请求抖动和长尾延迟。

先查看内存和交换区:

free -h
vmstat 1 15
cat /proc/pressure/memory
swapon --show

free -h中的used包含文件缓存,不能直接用“已用内存高”判断不足。更有价值的信号包括:

  • vmstat中的si、so持续不为零;
  • /proc/pressure/memory中的some或full在卡顿窗口显著升高;
  • 进程RSS持续增长,系统可回收内存下降;
  • 应用响应时间与换入换出同时出现尖峰;
  • 容器或systemd服务的内存上限低于主机可用内存。

还应检查是否为单个服务受到cgroup限制,而不是整台机器内存不足。不同发行版和运行环境的cgroup路径可能不同,先通过服务管理器或容器运行时确认限制,不要直接修改未知路径。

3.1 何时调整swappiness

如果业务确实出现交换活动,但仍有足够物理内存,可以把swappiness作为受控实验项。vm.swappiness=10只是常见参考值,并不适用于所有数据库和容器环境,也不建议为了“消除swap”而直接关闭交换区。

先保存当前设置:

sysctl vm.swappiness

在确认备份、变更范围和回滚文件后,再进行临时测试:

sudo sysctl -w vm.swappiness=10

临时设置重启后会失效,适合先验证。如果复测确认有帮助,再写入独立配置文件,并保留原文件备份:

sudo cp -a /etc/sysctl.d/99-io-tuning.conf \
  /etc/sysctl.d/99-io-tuning.conf.bak 2>/dev/null || true

printf 'vm.swappiness=10\n' | sudo tee /etc/sysctl.d/99-io-tuning.conf
sudo sysctl -p /etc/sysctl.d/99-io-tuning.conf

验证时同时观察内存压力、交换活动、应用P95和数据库缓存命中表现。如果延迟没有改善,或者数据库缓存命中率下降,应恢复备份文件中的旧值,再重新加载:

sudo cp -a /etc/sysctl.d/99-io-tuning.conf.bak \
  /etc/sysctl.d/99-io-tuning.conf
sudo sysctl -p /etc/sysctl.d/99-io-tuning.conf

如果备份文件不存在,说明该文件可能是本次新建的,回滚前应确认没有其他参数写入其中,再删除该文件并重启相关监控验证。不要把修改swappiness当作磁盘性能优化的替代方案。

4. 检查NVMe设备、文件系统与挂载路径

NVMe 4.0描述的是接口和链路能力,不代表所有业务都能获得同等提升。小块同步写、单线程读取、数据库锁等待、文件系统空间不足,都可能让应用感受不到顺序吞吐提升。

4.1 核对设备和文件系统状态

先确认业务实际使用的是哪一个设备,而不是只查看系统盘:

4. 检查NVMe设备、文件系统与挂载路径配图

lsblk -o NAME,MAJ:MIN,SIZE,FSTYPE,MOUNTPOINTS,ROTA,MODEL
findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS
df -hT
df -ih
dmesg -T | egrep -i 'nvme|blk_update|I/O error|timeout|reset|ext4|xfs'

重点检查:

  • 业务数据是否仍然写入旧HDD、网络文件系统或另一个慢盘;
  • 文件系统容量和inode是否接近耗尽;
  • 内核日志是否出现I/O error、timeout、reset或文件系统错误;
  • 数据库数据、WAL或日志是否集中在同一设备;
  • 设备路径是否存在多层加密、逻辑卷或虚拟化存储。

如果系统安装了nvme-cli,可以读取设备信息和健康状态:

command -v nvme
sudo nvme list
sudo nvme smart-log /dev/nvme0

/dev/nvme0只是示例,必须根据nvme list确认设备名。健康日志中的介质错误、警告温度、异常掉盘记录和可用寿命信息,应结合系统日志和业务时间线判断,不能只看一个字段下结论。

4.2 读取I/O延迟和队列

iostat -xz 1 10

在同一时间窗口中,如果业务P99、await、aqu-sz和进程阻塞状态同步上升,且CPU、内存、网络和数据库锁等待没有相同幅度的变化,存储路径才更可能是主要瓶颈。

不要把NVMe的%util=100作为唯一阈值。NVMe具备并行队列,单一利用率无法准确表达所有请求是否已经无法及时处理。应同时看读写类型、块大小、队列长度、读写延迟和业务响应时间。

4.3 用隔离文件做受控验证

需要区分“设备能力不足”和“业务访问模式不适合”时,可以在专用测试目录创建临时文件进行fio测试。测试目录必须是确认无业务数据的路径,不能把/dev/nvme0n1等裸设备直接作为目标,也不要在高峰期运行写入型测试。

下面是一个仅供参考的文件级测试:

mkdir -p /srv/fio-test

fio --name=nvme-check \
  --filename=/srv/fio-test/fio.bin \
  --size=2G \
  --rw=randrw \
  --rwmixread=70 \
  --bs=4k \
  --iodepth=32 \
  --numjobs=1 \
  --direct=1 \
  --time_based \
  --runtime=60 \
  --group_reporting

这个结果只能说明该文件系统路径在当前条件下的表现,不能直接代表数据库、容器存储或多租户业务的实际延迟。完成后确认路径无其他文件,再删除测试文件:

rm -f -- /srv/fio-test/fio.bin

删除前必须核对路径,避免误删业务文件。若测试本身导致业务延迟升高,应立即停止测试进程,恢复业务负载,并检查磁盘队列是否回落。

5. 分析I/O请求形态:找出NVMe没有解决的“访问方式问题”

从HDD迁移到NVMe,改善最明显的通常是随机访问延迟和并发能力。但如果业务请求主要是同步写、频繁fsync、极小块随机写,或者应用只有一个串行线程,设备接口升级并不能消除所有等待。

用进程级数据确认是谁在读写:

pidstat -d -p ALL 1 10

结合应用日志和业务指标,重点区分以下几种模式:

I/O模式常见表现优先优化方向
大量4K至8K随机同步写w_await和队列升高,日志或事务提交变慢检查事务批量、日志刷盘、写入合并和应用提交频率
少量线程串行读写设备利用率不高,但单请求延迟明显检查应用并发模型和是否存在串行锁
大量顺序读写吞吐高,延迟不一定高检查批处理窗口、读写带宽和缓存策略
多个进程混合读写一个业务高峰影响全部服务分离高频日志、数据库数据和批处理任务
短时间突发写入队列周期性堆积后快速下降检查日志轮转、检查点、缓存刷新和任务调度

这里不要盲目提高I/O队列深度。队列深度增大可能提高吞吐,却也可能扩大尾延迟,尤其是数据库事务和在线请求混合时。正确做法是用接近真实业务的读写比例、块大小、并发数进行对照测试,并把P95/P99与吞吐量一起观察。

如果确认是日志、临时文件和数据文件争抢同一设备,可以考虑在有备份和维护窗口的前提下调整目录或卷的布局。但挂载点迁移涉及服务停止、数据同步和权限保持,不能直接修改/etc/fstab后重启验证。建议先复制配置、记录当前挂载关系,完成数据校验后再切换;失败时恢复原挂载配置和原数据路径,确认服务指向没有变化,再启动应用。

6. 分离网络等待与应用线程等待

当磁盘指标已经平稳,但请求仍然卡顿,下一步应观察网络连接和应用队列。应用访问远端数据库、对象存储或内部服务时,等待时间可能被误认为本地磁盘慢。

6.1 网络侧检查

查看网卡统计和TCP状态:

ip -s link
ss -s
ss -tanp

如果系统安装了sysstat:

sar -n DEV,TCP,ETCP 1 10

关注以下联动:

  • 网卡丢包、错误包或重传在卡顿窗口增加;
  • TCP Send-Q持续堆积,说明本端数据发不出去或对端读取不及时;
  • Recv-Q持续堆积,说明应用读取不及时;
  • 连接数接近上限,或大量连接处于异常等待状态;
  • 网络延迟升高时,磁盘await并未变化。

如果网络正常,但应用工作线程全部处于等待下游响应状态,问题更偏向应用调用链或下游服务。不要通过提高本地磁盘并发来解决网络队列问题。

6.2 应用侧检查

应用监控至少要能区分:

  • 请求排队时间;
  • 业务处理时间;
  • 数据库调用时间;
  • 外部服务调用时间;
  • 文件读写时间;
  • 活跃线程、空闲线程和连接池等待时间;
  • 超时、重试和熔断次数。

可以使用pidstat将系统进程与应用指标对齐:

pidstat -u -d -p <应用PID> 1 10

将<应用PID>替换为真实进程号。若应用P99升高,但该进程I/O量、磁盘延迟和CPU都稳定,同时线程池队列增长,应优先检查线程池、连接池、锁和下游超时配置。

优化顺序通常是:

  1. 找到P99上升最明显的接口或任务;
  2. 拆分排队时间、CPU时间、I/O时间和下游调用时间;
  3. 只调整一个线程池、连接池或超时参数;
  4. 在相同流量下观察延迟、错误率和队列长度;
  5. 出现超时增加、重试风暴或资源耗尽时立即恢复原值。

应用配置的回滚应使用原配置文件或版本控制,不要在故障期间同时修改线程数、连接数、磁盘队列和数据库缓存,否则无法判断哪项变更产生了结果。

7. 检查数据库:把锁等待和日志刷盘从“磁盘慢”中分离出来

数据库是NVMe升级后仍然卡顿的常见原因之一。数据库请求可能等待行锁、表锁、事务提交、日志刷盘、检查点或连接池,而不是等待数据页读取。

在数据库监控中同时查看:

  • 活动会话数与连接池使用率;
  • 锁等待数量和最长等待时间;
  • 慢查询P95/P99;
  • 缓冲池或共享缓存命中率;
  • WAL、redo或事务日志写入延迟;
  • 检查点频率和持续时间;
  • 临时表、排序、哈希操作是否大量落盘;
  • 数据文件、日志文件和临时文件所在设备的独立I/O指标。

判断关系可以这样建立:

  • 数据库锁等待上升,磁盘延迟平稳:优先处理事务范围、索引和并发更新,不要更换磁盘;
  • 日志刷盘延迟与w_await同步升高:检查日志设备、同步提交策略和事务批量;
  • 慢查询增加且缓冲命中率下降:检查执行计划、索引和内存配置;
  • 检查点期间写队列明显堆积:检查检查点参数、写入突发和存储带宽;
  • 数据库连接池排队,但数据库CPU和磁盘均不高:检查连接泄漏、长事务和应用池大小。

数据库参数调整属于高影响变更。修改缓存、日志提交、检查点或连接数前,应保存当前配置并确认数据库备份可用。每次只调整一个参数,在固定查询集和相近业务量下复测。如果错误率、锁等待或恢复时间变差,恢复原配置并按数据库产品的方式重新加载;涉及数据目录、日志目录或表结构的变更,应先在测试环境验证,不能以删除数据文件作为回滚手段。

复测与回滚:用同一组指标确认优化是否成立

完成某一层调整后,不要只看一个“更快”的命令结果,应重复相同的业务场景和采样窗口。至少保留变更前后两组数据:

类别变更前后都要记录的指标
业务吞吐量、平均响应、P95、P99、超时率、错误率
CPUus、sy、wa、st、运行队列
内存可用内存、PSI、换入换出、进程RSS
存储IOPS、吞吐、读写await、aqu-sz、设备错误
网络重传、连接数、Send-Q、Recv-Q
应用工作线程、任务队列、连接池等待、下游耗时
数据库锁等待、慢查询、缓存命中、日志刷盘、检查点

如果同等业务量下P99下降、错误率不升高,且造成卡顿的队列或等待指标同步回落,才可以认为优化方向有效。若吞吐量提高但P99明显恶化,说明可能是队列被扩大,应恢复并发或队列参数。

建议采用以下低风险变更顺序:

复测与回滚:用同一组指标确认优化是否成立配图

  1. 先采集基线,不修改生产配置;
  2. 优先处理明确的CPU、内存、网络、锁等待或错误日志;
  3. 再调整应用并发、数据库参数或I/O策略;
  4. 每次只改一个变量,并保留旧配置;
  5. 复测失败时立即恢复旧值;
  6. 确认业务、系统和数据库指标都稳定后,再保留变更。

下一次遇到“换成NVMe后仍卡顿”,不要只执行一次磁盘测速。应在同一个时间窗口内同时观察:业务P99与错误率、CPU运行队列与iowait、内存压力与swap、磁盘await与aqu-sz、TCP重传与连接队列、应用线程池,以及数据库锁等待和日志刷盘延迟。只有这些指标的变化方向能够相互印证,才能判断瓶颈究竟在磁盘,还是只是被磁盘现象掩盖的CPU、内存、网络、应用或数据库问题。

目录结构
全文