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

cgroup v2 限制服务内存并观察回收事件

来源:17golang原创

时间:2026-10-03 21:31:11 494浏览 收藏

服务内存异常时,先别急着把机器的总内存调大。cgroup v2 更适合把“这一个服务最多能用多少”和“达到压力后如何回收”拆成两层:用 memory.high 施加回收压力,用 memory.max 设置硬边界,再用 memory.current、memory.events 和 memory.events.local 判断究竟发生了什么。

要点速览
  • memory.high 是压力阈值,超过后会触发更重的回收和节流,不等同于立即 OOM。
  • memory.max 是硬上限,无法回收时可能在该 cgroup 内触发 OOM。
  • memory.events 默认包含层级统计,定位当前服务时还要对照 memory.events.local。

一、先确认主机真的使用 cgroup v2

先查看挂载点和可用控制器。以下命令只读系统状态,示例路径假定统一层级挂在 /sys/fs/cgroup。

# 确认目标目录是否为 cgroup2 文件系统
stat -fc %T /sys/fs/cgroup

# 查看当前层级可以向子 cgroup 提供哪些控制器
cat /sys/fs/cgroup/cgroup.controllers

# 查看根层级已经向子 cgroup 开启了哪些控制器
cat /sys/fs/cgroup/cgroup.subtree_control

第一条命令应看到 cgroup2fs。如果 cgroup.controllers 中没有 memory,先检查内核配置、挂载方式和上级层级,而不是直接向目标目录写入 memory.max。v2 的控制器需要由父层级通过 cgroup.subtree_control 提供给子层级。

cgroup v2 控制器准备与服务内存文件的静态结构说明图
图1:cgroup v2 的控制器准备、服务 cgroup 与内存接口文件之间的静态关系;这是原创结构说明图,不是终端截图。

二、为服务建立 memory.high 和 memory.max 边界

确认控制器可用后,创建一个只用于示例服务的叶子 cgroup。这里把可回收压力阈值设为 512 MiB,把硬上限设为 768 MiB;实际数值应根据服务稳定工作集、缓存策略和突发流量压测结果确定。

# 创建服务专用的 cgroup 目录
sudo mkdir -p /sys/fs/cgroup/demo-api

# 超过该值后增加回收压力和节流,但不直接等同于 OOM
echo 512M | sudo tee /sys/fs/cgroup/demo-api/memory.high

# 无法继续回收时的硬边界;max 表示不设硬上限
echo 768M | sudo tee /sys/fs/cgroup/demo-api/memory.max

# 读取内核实际接受的值,避免只相信写入命令没有报错
cat /sys/fs/cgroup/demo-api/memory.high
cat /sys/fs/cgroup/demo-api/memory.max

memory.high 适合做服务保护的第一道边界:它让超出阈值的工作负载承受更强回收压力。memory.max 才是硬限制,但内核文档也明确指出,某些情况下使用量可能暂时超过它,且部分分配路径不会按普通 OOM 方式处理。因此这两个值不能简单理解成“512 MiB 和 768 MiB 两个精确瞬时开关”。

三、让服务进程真正归属这个 cgroup

只创建目录并不会改变服务归属。对已经启动的单进程服务,可以把 PID 写入 cgroup.procs;systemd 管理的服务则更适合把限制写到 unit,让重启后仍然有效。

# 读取目标服务的 PID;这里的服务名仅作示例
pid="$(systemctl show -p MainPID --value demo-api.service)"

# 把主进程加入目标 cgroup,生产环境还要确认子进程继承和权限边界
echo "$pid" | sudo tee /sys/fs/cgroup/demo-api/cgroup.procs

# 核对 PID 当前是否已经出现在目标 cgroup
grep -w "$pid" /sys/fs/cgroup/demo-api/cgroup.procs

如果服务由 systemd 托管,持久配置可以使用资源控制属性:

# 为 unit 写入可持久化的内存压力阈值和硬上限
sudo systemctl set-property demo-api.service MemoryHigh=512M MemoryMax=768M

# 查看 systemd 展开的属性,确认 unit 层配置已生效
systemctl show demo-api.service -p MemoryHigh -p MemoryMax

手工写 cgroup 文件适合排查和一次性实验;长期运行的服务应让 systemd 或其他编排层成为唯一配置来源,避免重启、迁移和子进程管理把限制丢掉。

四、用 current 和 events 判断发生了哪类压力

先看当前使用量,再看事件计数。计数变化说明边界曾被触碰,但不能单独证明某一次请求就是唯一原因。

# 当前 cgroup 的内存使用量,单位为字节
cat /sys/fs/cgroup/demo-api/memory.current

# 读取包含子层级的事件统计
cat /sys/fs/cgroup/demo-api/memory.events

# 只读取 demo-api 自身产生的事件,便于排除子 cgroup 干扰
cat /sys/fs/cgroup/demo-api/memory.events.local

常见字段可以这样理解:

字段它说明什么排查重点
high内存使用触碰过 high 边界工作集是否长期高于阈值,是否出现持续节流
max使用量触碰过 max 边界回收是否跟不上,是否需要拆分缓存或降低并发
oomcgroup 内发生过 OOM 触发结合服务日志和进程退出时间判断影响面
oom_kill发生过 OOM kill优先保留计数与日志,不要只看一次 current
memory.current 与 memory.events 回收和 OOM 统计边界的静态结构说明图
图2:当前使用量、high/max 边界与 events 统计字段的静态关系;这是原创结构说明图,不是运行结果截图。

五、按照现象调整,而不是看到 max 就盲目放大

如果 high 持续增长而 oom_kill 不变,通常先检查工作集、缓存上限和并发,再评估是否提高 memory.high。如果 max、oom 或 oom_kill 增长,要把它当作硬边界告警,核对服务是否被杀、是否有子 cgroup 分摊,以及 memory.events 的层级统计是否把后代事件算进来了。

复查时至少保留三份信息:限制值(memory.high、memory.max)、当前值(memory.current)和事件快照(memory.events.local)。如果需要主动回收,memory.reclaim 可以触发目标 cgroup 的回收,但它是管理接口,不能用来伪造“发生了内存压力”的业务证据;回收结果也可能多于或少于请求值。

常见问题

memory.high 达到后会马上杀掉服务吗?

不会。它主要引入更重的回收和节流;真正的硬边界是 memory.max,也仍应结合事件和服务日志判断结果。

为什么 memory.events 和 memory.events.local 数字不一样?

memory.events 默认是层级统计,可能包含后代 cgroup;memory.events.local 只看当前 cgroup。服务拆分子组时应同时读取两者。

systemd 和手工写 memory.max 选哪个?

一次性定位问题可以手工写文件;长期服务优先使用 systemd 的 MemoryHigh、MemoryMax,让限制和 unit 生命周期一起管理。

最后用一张清单复查:控制器是否已向子层级开放、服务 PID 是否在目标 cgroup、high/max 是否符合工作集、events 是否按 local 与 hierarchy 分开解释。这样看到“回收事件增加”时,才能知道是在保护服务,还是已经撞上了硬边界。

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