概述
在 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 实现的共享内存,统计上计入
Cached 和 Shmem。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 -hcat /proc/meminfo
重点关注
MemTotal、MemFree、Cached、Shmem、Active(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 命令查看进程的 SHR 与 RSS,如果某进程 SHR 接近 RSS,可推断该进程大部分使用的是共享内存(如 mmap(MAP_ANON|MAP_SHARED) 方式申请的匿名共享内存)。匿名共享内存虽然统计上计入
Cached 和 Shmem,但在无 swap 设备时无法被回收。注意:
free 输出的 buff/cache 一列由 Cached + Buffers + Slab 计算得出,仅看该列数值会高估可回收内存量。步骤 3:检查 Direct Reclaim 是否频繁发生
通过
/proc/vmstat 的 allocstall 计数判断是否发生同步回收:grep allocstall /proc/vmstat
在异常期间多次采样,如果
allocstall 持续增加,说明系统正在频繁发生 direct reclaim,此时应用的内存申请会被阻塞。步骤 4:检查 kswapd CPU 使用率
通过
top 或 ps 观察 kswapd 内核线程的 CPU 使用率:top -p $(pgrep kswapd | tr '\\n' ',' | sed 's/,$//')
如果
kswapd0、kswapd1 等线程 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 命令判断可回收内存:free 的 buff/cache 列包含 Shmem,而匿名共享内存在无 swap 时不可回收。应结合 /proc/meminfo 的 Active(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+,对延迟敏感型业务影响显著。