登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

Linux cgroups v2 内存 怎样处理

来源:17golang原创

时间:2026-10-22 10:10:42 126浏览 收藏

Linux cgroups v2 内存问题,先不要把所有现象都叫作“内存不够”。正确的处理顺序是:先读 memory.current 建立当前用量,再用 memory.high 施加可恢复的回收压力,最后用 memory.max 作为硬边界;出现变慢、回收或进程被杀时,再结合 memory.events 和 memory.stat 判断原因。

这篇文章解决三个判断
  • memory.high 超限通常意味着回收和限流,不等于 OOM。
  • memory.max 是硬上限,无法回收到边界内时才会在该 cgroup 内触发 OOM。
  • memory.events 看事件,memory.stat 看构成,两者要和当前用量一起读。
cgroups v2 内存当前用量、high 限流和 max 硬边界的静态结构图
图1:cgroups v2 内存控制的静态说明图,展示 current、high 与 max 的边界关系,不是运行截图。

一、先区分当前用量和限制动作

排查一个 cgroup 的内存问题,第一组指标应该固定下来:memory.current 表示该 cgroup 及其后代当前使用的内存总量;memory.high 是超过后施加重回收压力的阈值;memory.max 是无法回收到边界内时触发 cgroup 内 OOM 处理的硬上限。三者的职责不同,不能只看一个数字下结论。

下面的例子假设工作负载位于 /sys/fs/cgroup/demo-worker。命令只展示读取和配置路径,实际操作前应确认该目录属于统一层级且当前进程具有相应权限。

# 先记录当前用量和两个边界,避免只凭进程 RSS 猜测
cg=/sys/fs/cgroup/demo-worker
printf 'current='; cat "$cg/memory.current"
printf 'high='; cat "$cg/memory.high"
printf 'max='; cat "$cg/memory.max"

# 记录事件快照,后续调整后用同样的字段做前后对比
cat "$cg/memory.events"
cat "$cg/memory.stat" | sed -n '1,20p'

基线至少保存两次:一次在业务平稳时,一次在症状出现时。若 current 接近 high 但 max 还很远,重点应放在回收压力和吞吐下降;若已经反复触碰 max,才进入硬上限排查。

二、用 memory.high 处理可恢复的压力

memory.high 更适合作为第一道治理边界。工作负载超过它时,内核会让该 cgroup 承受直接回收和限流压力,但这个边界本身不会调用 OOM killer。它的价值是给监控和管理程序留出时间:降低并发、释放缓存、迁移任务或回滚配置。

例如,先把测试工作负载的高水位设为 768 MiB,再观察事件和延迟变化:

# 设定可恢复的高水位;写入前确认这是测试或已批准的 cgroup
echo 768M > "$cg/memory.high"

# high 超限通常会增加 high 计数,用它和业务延迟一起判断是否过紧
grep -E '^(high|max|oom|oom_kill) ' "$cg/memory.events"

# 若需要撤销高水位,max 表示不设置这个边界
echo max > "$cg/memory.high"

如果 high 持续增长、请求延迟上升但 oom_kill 没有变化,典型解释是高水位过低或工作负载本来就需要更大的活跃集。此时应结合业务延迟、吞吐和回收行为逐步上调,而不是直接把 high 与 max 设成同一个值。

三、用 memory.max 设置最后一道边界

memory.max 解决的是“这个工作负载最多允许占用多少内存”。当使用量达到它且无法继续回收时,OOM 处理会被限制在对应 cgroup 的资源域内;在某些分配和并发时刻,观察到的用量也可能短暂超过该值。因此它是安全兜底,不是精确的实时仪表盘。

# 先保留一个比 high 更高的硬边界,避免回收压力立即演变成 OOM
echo 1G > "$cg/memory.max"

# 调整后同时观察当前用量和 OOM 相关事件
cat "$cg/memory.current"
grep -E '^(max|oom|oom_kill|oom_group_kill) ' "$cg/memory.events"

# 发生误配时可恢复为无限制,但这不是替代容量规划的长期方案
echo max > "$cg/memory.max"

下调 memory.max 时不要把“写入成功”当作“工作负载已经稳定”。内核还需要回收,无法满足边界时可能发生 OOM;生产环境应先通过 memory.current、事件计数和应用级错误率确认回滚窗口,再改变硬限制。

四、通过 memory.events 和 memory.stat 定位原因

读事件时,先看 high、max、oom 和 oom_kill。其中 high 增长说明曾经超过高水位并受到压力;max 增长说明触及硬边界;oom 说明发生了 OOM 处理尝试;oom_kill 则表示实际杀死进程的次数。若使用了 swap,还应注意相关 swap 事件字段是否增长。

memory.events 是层级统计,后代 cgroup 的事件也可能反映在上层;需要只看当前 cgroup 的本地事件时,可读取 memory.events.local。这能避免把子服务的压力误归因给父目录。

memory.events 与 memory.stat 联合定位 cgroups v2 内存问题的静态结构图
图2:从事件计数和内存构成定位问题的静态说明图,不是终端截图,也不代表已运行的测量结果。
# 先看层级事件,再看本地事件,区分父 cgroup 与当前目录的影响
cat "$cg/memory.events"
cat "$cg/memory.events.local"

# 按键读取构成,不依赖字段在文件中的固定顺序
awk '$1 == "anon" || $1 == "file" || $1 == "shmem" || $1 == "slab" {print}' "$cg/memory.stat"

如果 anon 占比高,通常要回到应用堆、线程栈或匿名映射寻找增长点;如果 file 占比高,则要判断文件缓存是否符合工作负载预期。memory.stat 的键可能随内核版本增加,脚本应按键查找,不要依赖固定行号。

五、把配置落成可回滚的排障步骤

  1. 先保存平稳期和异常期的 memory.current、memory.events、memory.stat。
  2. 只调整 memory.high,观察回收压力、延迟和 high 计数,确认是否是软边界过紧。
  3. 确认工作负载的合理峰值后,再把 memory.max 放在高水位之上作为最终保护。
  4. 出现 OOM 时,用 oom_kill 和本地事件确认杀进程发生在哪个 cgroup,再回到 anon、file 等构成字段。
  5. 每次只改一个边界,并保留可执行的回滚命令;不要把临时解除限制当成长期修复。

最常见的误区是把 memory.high 当成“超过就杀进程”,或把 memory.current 当成“应用真正需要的内存”。前者会导致告警和动作设计错误,后者会让缓存型工作负载被过度扩容。把边界、事件和构成放在同一张指标面板上,才能判断是配置问题还是应用增长问题。

相关问题

memory.high 超过后一定会 OOM 吗?

不会。它主要触发回收和限流,应该先关注延迟、吞吐和 high 事件。

为什么 memory.current 可能短暂超过 memory.max?

内核文档允许某些情况下出现临时超额,不能把一次读数直接当成稳定状态。

memory.events 和 memory.events.local 选哪个?

要看当前目录及后代的累计影响时看前者;只定位当前 cgroup 本地事件时看后者。

memory.stat 为什么不能按第几行解析?

字段是按键组织的,内核可能在中间增加新键,脚本应按字段名匹配。

资料依据:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>