帮你快速理解、总结文档立即下载

磁盘 IO 问题分析

最近更新时间:2026-08-27 18:07:01
我的收藏

概述

在 TencentOS Server 系统运维中,常见一类问题:系统内存使用率持续偏高时,会伴随出现与业务请求量明显不匹配的大量磁盘读 IO,同时应用延迟大幅增加。此类问题在数据库、大数据等内存密集型业务上尤为多见。本文提供一套通用的排查方法论,帮助定位"高内存 + 异常读 IO + 应用延迟"三者之间的关联根因,并给出对应的解决方向。

问题原理

内存回收机制:kswapd 异步回收 vs direct reclaim 同步回收

Linux 内核在内存紧张时会触发内存回收,分为两个阶段:
kswapd 异步回收:由内核线程 kswapd 在后台执行,提前回收内存以保持水位。此阶段对应用透明,不阻塞业务线程的内存申请。
direct reclaim 同步回收:当 kswapd 异步回收速度跟不上内存消耗速度,系统水位跌破阈值时,分配内存的线程会被迫在自身上下文中同步回收内存。此阶段会阻塞大部分线程的内存申请,直到回收出足够的内存。
如果频繁发生 direct reclaim,应用的感知是“卡死”或“挂起”,业务延迟随之显著增加。

Page Cache 的构成:file-backed page vs anonymous page

系统的 page cache 主要由以下几类内存页构成(可通过 /proc/meminfo 估算):
file-backed page:即 Active(file) + Inactive(file),是文件内容的内存缓存,与文件 I/O 性能直接相关。这部分内存可被内核回收。
Shmem:基于 tmpfs / 共享匿名 mmap 实现的共享内存,统计上计入 CachedShmem
anonymous page:即 Active(anon) + Inactive(anon),是进程私有匿名内存(如 malloc 分配的堆内存)。

匿名内存不可回收的问题(无 swap 时)

匿名内存的回收依赖 swap 机制:内核通过将匿名页写出(swap out)到 swap 设备来回收。当系统未配置 swap 设备时,匿名内存实际上是不可回收的(除非触发 OOM)。
这会导致一个隐蔽但严重的问题:free 命令输出的 buff/cache 一列看似有大量可回收缓存(因为 Cached 统计包含了 Shmem),但其中绝大部分可能是匿名共享内存,内核实际可回收的 file-backed cache 已经少得可怜。

Direct Reclaim 阻塞导致应用延迟

当 file-backed cache 被大量回收后,应用一旦访问的 page 不在 RAM 中,就会触发 pagefault 读盘。磁盘与内存的性能差距巨大(机械盘单次读延迟 10ms+,SSD ms 级),即使只访问少量 page,也可能导致应用延迟增加几百倍。同时,direct reclaim 同步回收会阻塞全部应用的内存申请,进一步加剧延迟。

诊断步骤

步骤 1:查看系统内存使用率

使用 free/proc/meminfo 确认内存整体占用情况:
free -h
cat /proc/meminfo
重点关注 MemTotalMemFreeCachedShmemActive(anon)Inactive(anon)Active(file)Inactive(file) 等字段。
如果内存使用率接近 100%,需进一步区分占用的是哪类内存。

步骤 2:分析 Page Cache 构成

通过 /proc/meminfo 估算 page cache 的构成:
file-backed cache = Active(file) + Inactive(file)
共享内存 = Shmem
匿名内存 = Active(anon) + Inactive(anon)
判断要点:
如果 Cached 数值很大,但 Active(file) + Inactive(file) 非常低,说明大部分 cache 是 Shmem(共享匿名内存),file-backed cache 已被大量回收。
结合 top 命令查看进程的 SHRRSS,如果某进程 SHR 接近 RSS,可推断该进程大部分使用的是共享内存(如 mmap(MAP_ANON|MAP_SHARED) 方式申请的匿名共享内存)。
匿名共享内存虽然统计上计入 CachedShmem,但在无 swap 设备时无法被回收。
注意free 输出的 buff/cache 一列由 Cached + Buffers + Slab 计算得出,仅看该列数值会高估可回收内存量。

步骤 3:检查 Direct Reclaim 是否频繁发生

通过 /proc/vmstatallocstall 计数判断是否发生同步回收:
grep allocstall /proc/vmstat
在异常期间多次采样,如果 allocstall 持续增加,说明系统正在频繁发生 direct reclaim,此时应用的内存申请会被阻塞。

步骤 4:检查 kswapd CPU 使用率

通过 topps 观察 kswapd 内核线程的 CPU 使用率:
top -p $(pgrep kswapd | tr '\\n' ',' | sed 's/,$//')
如果 kswapd0kswapd1 等线程 CPU 使用率持续偏高,说明系统内存紧张,异步回收已全速运转但仍然跟不上内存消耗,即将或已经进入 direct reclaim 阶段。

步骤 5:用 iotop 确认 IO 来源

使用 iotop 观察磁盘 IO 来源:
iotop -oPa
重点关注以下情况:
Actual DISK READ 明显大于各进程 DISK READ 之和:这是一种正常现象,原因在于 iotop 的两个指标数据来源不同:
Actual DISK READ:从 /proc/vmstat 读取,表示块设备与硬件之间的实际读写带宽。
Total DISK READ:从 /proc/<pid>/io 读取,表示线程对应的 IO。
两者差异的常见原因:
cached IO 会记录在 /proc/<pid>/io,但通常不同时记录在 /proc/vmstat
进程或线程之间的 cached IO(如管道、socket)会记录在 /proc/<pid>/io,但不会记录在 /proc/vmstat
/proc/<pid>/io 记录的是文件内容 IO,不记录文件系统元数据 IO(如删除文件时只涉及元数据变更,/proc/<pid>/io 变化很小,但 /proc/vmstat 会记录)。
没有对应 pid 的 IO,例如系统 cache buffer 从磁盘加载到内存,也会出现 Actual 大于 Total 的情况。
当 Actual DISK READ 明显大于 Total DISK READ 且找不到对应线程时,这通常意味着 IO 是由内核 routine 触发的(如 pagefault 读盘、内存回收过程中的回写),不属于任何一个用户态线程,因此 iotop 无法关联到具体 pid。

根因分析

综合以上诊断步骤,问题的根因链条如下:
匿名内存持续增长:应用通过 mmap(MAP_ANON|MAP_SHARED) 等方式大量申请匿名共享内存,占用系统大部分内存。
匿名内存不可回收:在未配置 swap 设备的情况下,匿名内存无法被内核回收。
file-backed cache 被挤压回收:内核内存紧张时,只能回收 file-backed page cache(包括线程 VMA 的代码段内存),导致可用的文件缓存急剧减少。
pagefault 读盘引发大量读 IO:应用访问的 page 不在 RAM 中时,触发 pagefault 从磁盘读取,产生大量磁盘读 IO,且该 IO 由内核 routine 触发,iotop 无法关联到具体进程。
direct reclaim 阻塞加剧延迟:kswapd 异步回收不足以缓解内存压力,系统进入 direct reclaim 同步回收,全部应用的内存申请被阻塞,业务延迟进一步增加。
简言之:匿名内存占满 > file-backed cache 被回收 > pagefault 读盘 > 大量读 IO + direct reclaim 阻塞 > 应用延迟激增

解决方案

根据根因,可从以下几个方向解决:

调整应用内存使用

排查应用是否存在内存泄漏或过度使用匿名共享内存的情况。
优化应用的内存申请方式,避免大量 mmap(MAP_ANON|MAP_SHARED) 占用。
对内存使用设置上限(如 cgroup 限制),防止单一应用耗尽系统内存。

增加系统内存

如果业务内存需求合理但超出当前服务器容量,考虑扩容物理内存。

配置 swap 设备

为系统配置 swap 设备(可以是 swap 分区或 swap 文件),使匿名内存具备回收能力。
警告:swap 会引入磁盘 IO 开销,需根据业务特性权衡 swap 大小和 swappiness 参数。对于延迟敏感型业务,swap 作为兜底手段而非常规回收途径更合适。

其他注意事项

不要仅依赖 free 命令判断可回收内存:freebuff/cache 列包含 Shmem,而匿名共享内存在无 swap 时不可回收。应结合 /proc/meminfoActive(file) + Inactive(file) 判断实际可回收的 file-backed cache。
iotop 中 Actual DISK READ > Total DISK READ 是正常现象:当 IO 由内核 routine 触发(如 pagefault 读盘)时,没有对应 pid,Actual 会大于 Total。这不是 iotop 的 bug,而是数据来源差异导致的。
allocstall 计数是判断 direct reclaim 的关键指标:异常期间持续采样该计数,若持续增长即可确认同步回收正在发生。
kswapd CPU 使用率偏高是内存紧张的早期信号:在 direct reclaim 发生前,kswapd 已全速运转,应作为预警指标关注。
page cache 过少会直接导致 IO 性能劣化:file-backed cache 过低时,即使少量 page 访问也会触发磁盘读,机械盘单次延迟可达 10ms+,对延迟敏感型业务影响显著。