返回列表 发布新帖
查看: 18|回复: 0

内存管理与OOM实战:vm.swappiness与压测指北

988

主题

0

回帖

833

积分

高级会员

积分
833
发表于 2026-6-25 09:25:01 | 查看全部 |阅读模式
最近在做一轮内存相关的压测,顺手把 vm.swappiness 这件事翻出来又折腾了一遍。很多人把 OOM 归咎于“内存不够”,但经验告诉我,真正让服务雪崩的,往往是内存回收策略和访问模式之间的错配——换句话说,压测怎么打、swappiness 怎么配,比你多给 2GB 内存更关键。

先说结论:vm.swappiness 不是性能开关,而是“风险偏好”。它定义了内核在“丢页面缓存”与“换出匿名页(比如堆里对象)”之间的倾向。偏高(如 60、100)意味着更愿意把匿名页换到 swap,保住 page cache;偏低(如 10、1、甚至 0)则尽量不换出匿名页,宁可更积极地回收 cache。听上去简单,但一上压测,差异就来了:IO 密集型服务(大量顺序读写文件、依赖页缓存)在高 swappiness 下更稳定,延迟尾部更平滑;而 GC 频繁的内存计算型服务,高 swappiness 反而会在某个阈值后突然出现延迟尖刺,因为活跃对象被换出,再被触发访问时抖成一片。

很多团队压测时忽视了访问局部性。典型坑:把数据热集扩大到超过物理内存的 1.2-1.5 倍,然后用并发扫全量做“压力测试”。这类压测更像坏场景合成器:会逼内核在匿名页和文件缓存之间来回拉扯。此时如果 swappiness 偏高,你会看到系统 swap in/out 持续,vmstat si/so 不断,CPU 上升但大头是 kswapd 和软中断;而 swappiness 拉低,系统会更快触发文件缓存回收,IO 压力反而上升,尾延迟可能来自磁盘抖动。两害相权取其轻,要结合业务优先级:数据一致低延迟优先,还是吞吐优先。

再谈 OOM。OOM Killer 不是内存不够时的“最后一击”,而是你页面回收策略失败的报警器。压测里常见三个触发路径:1) 突发创建大量匿名内存(比如大批协程分配 buffer),匿名页不能回收到文件,回收失败;2) cgroup 限额过紧,容器内 swappiness 调得再漂亮,也抵不过硬上限;3) THP 与内存碎片化,导致无法满足大页分配请求。调低 swappiness 能推迟 swap 带来的延迟问题,但在匿名内存激增时,反而更早撞上 OOM。很多人误以为把 swappiness 设成 1 或 0 就“更安全”,实际上你只是选择了“要么快挂、要么慢抖”的两难。

压测方法上,我现在更建议分层验证:先跑“稳态热集”——热集不超过物理内存的 60-70%,观察 swappiness=60 与 swappiness=10 的对比,记录三件事:P95/P99 延迟、context switch 和 kswapd CPU。其次再跑“挤压测试”——热集提升到 90-110%,逐步打开 swap,观察 si/so 拐点和应用端 GC/暂停峰值。最后才是“失稳测试”——超配到 120-140%,用低 swappiness 看 OOM 触发阈值,用高 swappiness 看尾延迟崩坏阈值。把三个阈值画在一张图上,比任何单点结论都更能指导生产参数。

工程落地还有几条经验:对于依赖文件缓存的服务(Nginx 静态分发、日志聚合、对象存储网关),维持默认或略高的 swappiness,多给页缓存缓冲空间;对于 JVM、Spark 类内存计算服务,在容器里配 cgroup memory.high 与 memory.max 的缓冲梯度,配合较低的 swappiness,避免热对象被换出,同时明确 GC 堆外内存水位,别让本地缓冲把匿名内存顶爆。再者,别忘了

再者,别忘了监控维度要对齐内核视角:不仅看应用指标,还要盯 vmstat、/proc/meminfo、pressure stall information(PSI)和 cgroup 事件。尤其是 PSI 的 memory some/full,可以比 P99 延迟更早预警系统处于回收/抖动态;再配合 pgscan/pgsteal、kswapd vs direct reclaim 次数,能判断是温和回收还是已经进入直回收的“痛苦区”。如果你的盘是云盘或网络盘,iostat 的 await 和 svctm 上扬,通常意味着“用低 swappiness 顶住匿名页”带来的副作用已经开始吞噬尾延迟了。

另一个被低估的点是透明大页(THP)和分页策略的相互作用。很多发行版默认 madvise 或 always,和内存型服务的访问模式并不匹配。压测里如果看到 khugepaged 占用异常、分配退化或 compaction 开销激增,先把 THP 调到 madvise,仅对真正收益明显的批处理路径开启,再观察匿名页换出是否缓解。顺带一提,NUMA 的自动平衡在高压下也会放大抖动,跨节点访问叠加 swap in/out,延迟成倍跳;多节点机器上建议在压测时固定内存亲和或限制节点范围,减少“错位抖”。

参数层面,我更倾向“可解释的保守主义”:生产先以 swappiness=30-60 起步,结合业务画像再往两端微调,而不是一上来就 0 或 100。把内存水位分成三段:常态水位(90%)。在警戒水位启用更积极的 cache 回收与限流(例如降级部分批量任务、封顶并发),在挤压水位触发速率限制与降质策略,必要时允许适度换出,给系统一个“可喘息的台阶”,避免直接撞 OOM。很多时候,你需要的不是一个神奇的 sysctl,而是一条明确的“水位-动作”表。

最后,把压测当成对“风险偏好”的校准:你的系统是愿意用可预期的尾延迟抖动,换更低的 OOM 概率,还是反过来?vm.swappiness 只是旋钮之一,和数据冷热分层、对象生命周期管理、cgroup 限额、THP/NUMA 策略一起,构成了一套联动机制。把指标画清楚、阈值跑出来、动作定下去,OOM 就不再是“黑天鹅”,而是被纳入预案的可控事件。
回复 转播

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关灯 在本版发帖
扫一扫添加微信客服
QQ客服返回顶部
快速回复 返回顶部 返回列表