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

Linux cgroup v2 内存限流怎么判读:memory.high、memory.max 与 memory.events 实战

来源:17golang原创

时间:2026-08-18 13:05:23 486浏览 收藏

线上跑的 worker 服务突然卡慢,很多人第一反应去查宿主机总内存,最后发现宿主机内存根本没占满。这种场景十有八九是服务所在的 cgroup v2 已经碰到了 memory.high,所有任务被迫进入内存回收和限流状态;内存占用接着往上涨的话,才会触到 memory.max,触发 cgroup 内部的 OOM 流程。要是直接把这两个边界混统称成「内存上限」,排障的时候大概率走弯路。

要点速览
  • memory.high 适合做可观测、可回退的内存节流,不直接触发 OOM killer。
  • memory.max 是硬兜底,触顶且无法回收时才可能在该 cgroup 内触发 OOM。
  • memory.events 中的 highmaxoomoom_kill 要结合时间顺序判断。
  • 调参前先记录 memory.currentmemory.peak 和事件计数,回滚时只恢复边界值。

先从症状判断:慢了,还是已经被杀

假设你的服务部署在 /sys/fs/cgroup/demo.slice/worker.service 路径下,先别急着把内存限制直接拉满改成 max,第一时间同时读取当前用量、峰值和事件计数:

cg=/sys/fs/cgroup/demo.slice/worker.service
cat "$cg/memory.current" "$cg/memory.peak"
cat "$cg/memory.high" "$cg/memory.max"
cat "$cg/memory.events"

如果 high 持续增加而 oom_kill 没有变化,优先判定是服务被节流、内存回收压力变大导致的延迟上涨;如果 maxoomoom_kill 一起增长,再去排查具体被终止的进程和当时的请求峰值。

Linux cgroup v2 分层内存边界:memory.high 节流后仍增长,memory.max 作为 OOM 兜底

memory.high 和 memory.max 不是同一种上限

用 memory.high 把失控增长变成可观测压力

memory.high 是内存使用的节流边界。用量超过它之后,cgroup 内的所有任务会承受更重的内存回收压力,接口延迟大概率会上升,但这个边界本身不会主动调用 OOM killer。把它设置在服务正常工作集的上限之上,相当于先给服务自动降速,给监控告警和后续扩容留出缓冲时间。

echo 768M > "$cg/memory.high"
echo 1G   > "$cg/memory.max"

上面列的数值只是演示示例,不能直接照搬套到生产环境。要先观察一段时间内的 memory.peak,再结合业务峰值、并发量和宿主机剩余资源余量来确定最终值。只设置 memory.max 而没有配套的 memory.high,通常会导致第一次明显告警出现的时候,服务已经离 OOM 非常近了。

用 memory.max 保护整台机器

memory.max 是硬内存限制。达到这个边界之后如果内存回收没法把实际用量压回去,内核会直接在当前 cgroup 内走 OOM 处理逻辑;它不等同于宿主机剩余内存的数值,也不会保证每一次内存分配失败都会立刻表现为完整的进程终止动作。

排查的时候要把三件事分开验证:服务响应是不是真的变慢了、cgroup 内存占用是不是接近硬边界、有没有真的发生 OOM 杀进程动作。只盯着应用日志里的超时报错,没法凑齐这三组完整证据,很容易误判根因。

memory.events 怎么读出真正的因果链

这个事件文件的计数是累计叠加的,单次读取只能告诉你这类事件之前发生过,没法直接给出发生的具体时间点。比较实用的做法是调整任何内存边界之前先存一份基线快照,过几分钟再对比两个快照的增量:

cat "$cg/memory.events" > /tmp/worker-memory-events.before
# 观察一段业务流量后再次读取
cat "$cg/memory.events"
字段含义排障动作
high超过 high 边界并发生节流或直接回收核对接口延迟、回收压力和工作集增长情况
max用量接近或越过 max 边界核对内存峰值、突发请求量和当前限制值
oom分配接近失败,进入 OOM 状态关联内核日志和对应时刻的进程状态
oom_killcgroup 内有进程被 OOM killer 终止留存对应时间段的服务日志,确认后续恢复和重启策略
memory.events 事件判读:从 high 节流到 max、oom 和 oom_kill 的分支处理

一套可回滚的处理步骤

1. 先留证,再改一个边界

提前记录当前的限制值、内存用量、峰值、事件文件快照和对应时间段的服务日志。不要同时修改 high、max、并发配置和缓存大小多组参数,不然就算后面延迟恢复了,你也没法定位到底是哪一项调整起了作用。

2. high 持续增长时先保护请求延迟

如果只是 high 增长,可以先调低 worker 并发数或者暂停非关键的后台批处理任务,再观察 memory.current 有没有回落。确认工作集本身没有继续异常上涨之后,再决定是调高 high 边界、缩减内存缓存占用,还是拆分服务实例。

3. 接近 max 时保留硬兜底

不要为了让服务能继续跑就直接删掉 memory.max 这个硬限制。更稳妥的操作是先把突发流量的来源压下去,确认应用可以正常释放占用的内存之后,再小幅调整 max 数值,接着持续观察 oomoom_kill 的增量变化。

4. 异常扩大后按原值回滚

cat "$cg/memory.high" "$cg/memory.max"
echo 768M > "$cg/memory.high"
echo 1G   > "$cg/memory.max"

如果你的服务是由 systemd 托管的,长期生效的配置应该写到 unit 的资源控制项里,通过 systemctl daemon-reload 和服务重载重启的流程正式生效;临时直接写入 cgroup 对应文件的方式只适合应急验证,不能当成永久配置方案。

几个容易混淆的边界

  • high 计数上涨不等于 OOM:它大概率只是说明业务正在被内存节流,还没到进程被杀的阶段。
  • max 计数上涨不等于已经杀进程:还要看 oomoom_kill 的数值变化。
  • events 所有字段都是累计值:发布服务或者调整参数之后要及时保存前后的快照,用增量差值而不是绝对值做判断。
  • 层级统计要留意:memory.events 默认会累加所有子 cgroup 的层级事件;如果只想看当前层的事件统计,去检查同目录下对应的 memory.events.local 文件即可。

相关问题

memory.high 设成和 max 一样的值可以吗?

可以,相当于关掉了提前节流的边界,但这样就失去了提前暴露内存压力的预警信号。生产环境运行的核心服务通常还是要留一个合理的 high 阈值,搭配监控一起使用。

memory.max 触顶一定会杀掉主进程吗?

不一定。会不会最终触发 OOM kill 取决于内存回收是否成功、内存分配的类型和当前 cgroup 内的进程状态,最终判定结果要以事件文件和内核日志的记录为准。

为什么 memory.events 的 high 字段数值一直增加?

说明内存用量多次越过 high 边界,反复触发节流或者直接回收逻辑。先排查业务工作集有没有异常增长,再核对并发数、内存缓存和后台突发任务的运行情况。

systemd 托管的服务应该在哪里配置这两个参数?

直接在对应 unit 的资源控制配置段里设置 MemoryHigh 和 MemoryMax 即可;临时修改 cgroup 文件的操作只适合验证当前问题现象,不能当成长期方案。

Linux cgroup v2 的内存治理可以整理成一条清晰的操作链路:用 memory.high 提前减速留出缓冲,用 memory.events 留存完整事件证据,用 memory.max 做最后兜底保护。排障的时候始终保留参数调整前后的快照,后续处理结果才能复盘、可回滚。

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