三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Linux 直接内存回收拖高 P99:用 vmstat、perf 和 NUMA 证据定位

Linux 直接内存回收拖高 P99:用 vmstat、perf 和 NUMA 证据定位

Linux 直接内存回收拖高 P99:用 vmstat、perf 和 NUMA 证据定位

1. 高并发服务中的 P99 抖动:直接内存回收现场诊断

Gateway 出现 P99 尖峰时,先对齐应用 Trace、GC、数据库等待、vmstatperf。下面的输出是构造输入,目的是说明如何确认直接回收,而不是提供一组“正常值”。

在排查此类问题时,前端与业务层容易误判为数据库锁竞争或应用层垃圾回收(GC)停顿。

如果数据库和 GC 没有同步变化,而 Sys CPU、阻塞进程和直接回收指标在同一窗口上升,才把内存回收列为重点。具体比例从目标主机采集,单个 CPU 数值不能独立证明根因。


2. 现场排查:用 sar、vmstat 与 perf 锁定内核堆栈

为定位 Sys CPU 的高消耗根因,需要使用 Linux 系统诊断工具链提取现场证据。

首先在延迟抖动发生期间运行vmstat 1观察系统物理内存与页面交换动态:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 3 0 0 185420 12480 3241500 0 0 0 120 18450 32410 42 48 10 0 0 8 2 0 98210 12480 3241800 0 0 0 4500 24510 48920 38 52 10 0 0 <-- 产生卡顿 1 0 0 245120 12480 3242100 0 0 0 0 15200 28100 45 5 50 0 0

观察数据变化:free空闲内存迅速降至 98MB 左右,同时b(阻塞进程数)升至 2,sy内核态 CPU 占用率上升至 52%。

随后,开启perf top深入内核态函数级别的热点捕获:

# 捕获内核 CPU 热点函数 10 秒 sudo perf top -e cpu-clock -p $(pgrep -f gateway) --min-percent 2

采集中捕获到的内核堆栈高频热点函数如下所示:

Overhead Shared Object Symbol 38.45% [kernel] [k] try_to_free_pages 22.10% [kernel] [k] get_page_from_freelist 14.32% [kernel] [k] _raw_spin_lock_irqsave 8.12% [kernel] [k] shrink_inactive_list

根据追踪结果:占用系统 CPU 的主要逻辑是 Linux 内核在执行try_to_free_pages——即Direct Reclaim(直接内存回收)

mallocmmap不一定立即分配物理页;实际分配常在缺页或后续访问时发生。内存紧张时,分配路径可能进入 direct reclaim 并阻塞业务线程,回收是否涉及页回写取决于页类型和存储状态。是否造成卡顿应通过 PSI、缺页、回收与 I/O 指标共同判断。


3. 内核机制拆解:内存分配水位与 NUMA 陷阱

系统在后台存在kswapd0异步回收进程的情况下,仍触发同步 Direct Reclaim 的根源在于 Linux 内核 Buddy System(伙伴系统)的水位控制机制。

Linux 内核将物理内存划分成多个 Zone(如 Zone NORMAL、Zone DMA32),每个 Zone 维护三条水线:WMARK_MINWMARK_LOWWMARK_HIGH

分配路径会先检查各 Zone 的水位和可用页,必要时触发回收或回退到其他内存域。

此外,在多路 CPU 服务器上,NUMA 架构下的zone_reclaim_mode配置也是常见隐患。当 NUMA Node 0 内存不足时,若sysctl vm.zone_reclaim_mode设置为1,内核将优先在 Node 0 本地强行回收 Page Cache,而不会跨节点调用 Node 1 的空闲内存,从而推高局部回收延迟。


4. 优化落地:可复现的 Linux 内核内存调优 Shell 脚本

明确根因后,调优主要包含以下步骤:

  1. 提高异步回收水位线watermark_scale_factor,提升kswapd提前响应阈值;
  2. 关闭 NUMA 节点的强行本地回收 (zone_reclaim_mode = 0);
  3. 调整dirty_ratio避免脏页堆积打爆磁盘导致回收阻塞。

可使用如下 Shell 脚本固化内核配置:

#!/bin/bash # Linux 内核内存回收与 P99 延迟专项调优脚手架 set -e echo "=== 开始应用 Linux 内核内存调优参数 ===" # 1. 关闭 NUMA 节点内强行内存回收 (避免本地回收引发的巨大阻塞) sysctl -w vm.zone_reclaim_mode=0 # 2. 调大 kswapd 异步回收水线距离 (默认 10,设为 200 相当于预留 2% 物理内存缓冲区) sysctl -w vm.watermark_scale_factor=200 # 3. 避免内存过度 Overcommit 导致 OOM sysctl -w vm.overcommit_memory=1 # 4. 控制脏页回写水线,防止大块写脏页卡死文件系统 LRU 扫描 sysctl -w vm.dirty_background_ratio=5 sysctl -w vm.dirty_ratio=10 # 5. 适度保留目录项与 inode 缓存 (默认 100) sysctl -w vm.vfs_cache_pressure=50 # 固化到 /etc/sysctl.d/99-memory-optimization.conf 防止重启失效 cat << EOF > /etc/sysctl.d/99-memory-optimization.conf vm.zone_reclaim_mode = 0 vm.watermark_scale_factor = 200 vm.overcommit_memory = 1 vm.dirty_background_ratio = 5 vm.dirty_ratio = 10 vm.vfs_cache_pressure = 50 EOF sysctl -p /etc/sysctl.d/99-memory-optimization.conf echo "=== 内存调优参数生效完毕,正在检查当前水位状况 ===" cat /proc/zoneinfo | grep -E "Node|pages free|min|low|high" | head -n 15

5. 效果验收:指标来源与对照条件

用目标环境可承受的阶梯负载做对照测试,记录参数调整前后的指标:

性能指标采集来源对照时固定的条件
API P99 / P999入口指标、负载工具请求集、到达率、实例资源与工作集
Sys CPU 与热点容器指标、perf采样时长、内核版本和后台任务
Direct Reclaim/proc/vmstat、tracepoint内存压力、Page Cache 与 NUMA 绑定
kswapd 回收与扫描效率vmstat、内核 tracewatermark 候选值和相同负载阶段

调参是否减少 Direct Reclaim,要比较相同负载下的回收频次、Sys CPU 与延迟分位数。若工作集或流量变化,对照结果无效。


6. 总结收尾:遇到卡顿先查哪里

当高并发应用出现卡顿而应用日志没有直接线索时,可按以下顺序定位:

  1. 检查vmstatsyCPU 的异常抖动;
  2. 使用perf top确认是否存在try_to_free_pages内核调用;
  3. 核查 NUMAzone_reclaim_mode与内存水位配置。

理解 Linux 内核内存回收的水位逻辑,能够为解决高并发场景下的随机延迟提供明确的工程路径。

← 返回列表