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

cgroup v2 memory.high怎么配置或排查

来源:17golang原创

时间:2026-09-13 04:50:04 420浏览 收藏

很多运维和开发同学刚接触cgroup v2的时候,经常碰到memory.high参数配置完没效果,或者业务进程没到硬限制就被触发内存回收的情况,顺着常规配置步骤和排查路径走,基本都能定位到问题。

配置cgroup v2的memory.high前要先确认当前系统已经启用cgroup v2统一层级,排查时优先核对当前cgroup目录下的内存统计项、当前触发的水位阈值,再逐层校验父cgroup的配置继承规则即可。

如果服务进入 cgroup v2 后只是变慢,却没有被 OOM 杀掉,先看 memory.high。它是“超过后施加回收压力并节流”的边界,默认值是 max;真正的硬内存上限是 memory.max。因此排查重点不是盯着一个剩余内存数字,而是确认进程归属、当前用量、high 事件以及内存压力是否同时出现。

要点速览
  • memory.high 超限会让该 cgroup 承受直接回收和节流,通常不会直接触发 OOM killer。
  • 直接写 cgroup 文件时使用字节数;例如 512 MiB 应写成 536870912,不是带单位的字符串。
  • memory.events.local 看本组,memory.events 还会包含子树;两者都要和 memory.statmemory.pressure 对照。

memory.high 控制的是节流,不是 OOM

Linux 内核把 memory controller 的几个边界分成了不同职责。memory.lowmemory.min 是回收保护,memory.high 是超限后的节流边界,memory.max 才是无法回收时可能进入 cgroup OOM 的硬限制。把二者混用,是“服务没有退出却延迟暴涨”这类现象最常见的起点。

接口作用排查时关注什么
memory.high超过后加大回收压力并节流memory.events(.local)high
memory.max无法回收时限制用量,可能触发 OOMmaxoomoom_kill
memory.current当前 cgroup 及其后代的内存用量是否持续越过 high 边界

这也解释了一个容易误判的结果:memory.current 高于 memory.high 并不等于设置失败。高边界允许在极端情况下被暂时突破,关键证据是工作负载是否被持续节流,以及事件计数是否增长。

先把 memory.high 写进正确的 cgroup

先确认统一层级和控制器是否可见,再确认目标 PID 的归属。下面的示例只针对已经存在的 cgroup v2 挂载点;变量写法方便在测试机上替换目录,不要把宿主机根 cgroup 当成业务组直接修改。

# 确认 cgroup v2 挂载点和 memory 控制器是否可见
CG=/sys/fs/cgroup/demo-app
test -f /sys/fs/cgroup/cgroup.controllers || { echo "不是预期的 cgroup v2 根目录"; exit 1; }
grep -qw memory /sys/fs/cgroup/cgroup.controllers || { echo "memory 控制器不可用"; exit 1; }

# 直接写入 cgroup 文件时使用字节数:512 MiB = 536870912 字节
HIGH_BYTES=$((512 * 1024 * 1024))
printf '%s\n' "$HIGH_BYTES" | sudo tee "$CG/memory.high" >/dev/null
cat "$CG/memory.high"

# 先确认目标进程确实属于该组,再观察它的后续行为
PID=12345
grep -q "0::/demo-app" "/proc/$PID/cgroup" || { echo "PID 不在目标 cgroup"; exit 1; }
printf '%s\n' "$PID" | sudo tee "$CG/cgroup.procs" >/dev/null

如果 memory.high 不存在,通常不是“参数拼错”,而是 memory 控制器没有在父级的 cgroup.subtree_control 中启用,或者目标目录不是你以为的 v2 层级。cgroup v2 的控制器启用遵循自上而下的约束,先看 cgroup.controllers 和父目录的 cgroup.subtree_control,再处理权限。

cgroup v2 中应用进程、memory 控制器、memory.high、memory.current 与 memory.max 的静态关系框图
图1:memory.high 配置示意图,展示工作负载与 cgroup 内存接口的静态关系,不是实际终端截图。

memory.events 和 PSI 怎么定位瓶颈

设置完成后不要只重复读取阈值。memory.current 回答“现在用了多少”,memory.events.local 回答“本组发生了什么”,memory.stat 帮你拆分匿名内存、文件缓存、内核和 socket,memory.pressure 则反映任务因内存不足而等待的压力。

# 读取同一 cgroup 的用量、局部事件和压力;命令只读不修改配置
CG=/sys/fs/cgroup/demo-app
printf 'memory.current='; cat "$CG/memory.current"
printf '\nmemory.high='; cat "$CG/memory.high"
printf '\nmemory.max='; cat "$CG/memory.max"

# local 只看本组,memory.events 则可能包含后代 cgroup 的计数
cat "$CG/memory.events.local"

# 按键名读取统计,避免依赖 memory.stat 的显示顺序
awk '$1 ~ /^(anon|file|kernel|sock|shmem)$/ { print }' "$CG/memory.stat"
cat "$CG/memory.pressure"

判断可以按这个顺序进行:如果 memory.current 接近或超过 memory.high,同时 memory.events.localhigh 增长,说明本组确实反复进入高边界节流;如果只有 memory.events 增长而 local 不变,优先检查子 cgroup。若 maxoomoom_kill 增长,问题已经越过 memory.high,应该回到 memory.max 与工作负载峰值排查。

memory.stat 还能缩小方向:anon 持续上升更像进程堆或匿名映射增长,file 偏高则要看页缓存和 tmpfs,kernelsock 异常时不要只调高应用阈值。PSI 的 somefull 持续升高,才说明节流已经转化为可感知的等待;单独一次 high 计数不能代表服务一定需要更多内存。

Linux cgroup v2 中 memory.events.local、memory.events、memory.stat、memory.pressure 与 memory.current 的排查关系框图
图2:memory.high 排查示意图,展示用量、事件、组成和 PSI 之间的静态判断关系,不是实际运行结果。

常见问题

memory.high 可以直接写 512M 吗?

直接写 cgroup v2 接口时按内核文档使用字节数,建议先用 shell 算出整数再写入。systemd 的 MemoryHigh=512M 属于上层配置语法,不能据此推断底层文件也接受相同单位。

为什么 memory.current 超过 memory.high 还没有被杀?

这是预期语义。memory.high 主要触发回收压力和节流,不负责直接调用 OOM killer;需要硬上限时才检查 memory.max,并同时观察 oom 相关事件。

应该读 memory.events 还是 memory.events.local?

要判断当前目录自身,优先读 local;要观察整棵子树,读 memory.events。两者的层级口径不同,混着比较会把子服务的 high 事件误算到当前服务。

实际调参时,先记录业务低峰和峰值下的四组证据,再逐步调整 memory.high;不要用一次偶发计数替代持续压力判断。若由 systemd 管理服务,还应把 unit 的 MemoryHigh/MemoryMax 与实际 cgroup 文件对应起来,避免运行时手改被下一次部署覆盖。

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