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

resource-control 怎样限制服务内存

来源:17golang原创

时间:2026-10-25 14:08:02 425浏览 收藏

在 systemd 服务的 [Service] 段设置 MemoryHigh= 和 MemoryMax=,可分别建立回收节流阈值和内存兜底上限;需要约束交换空间时,再单独配置 MemorySwapMax=。本文以 cgroup v2 的内存控制器为前提,限制对象是服务所在资源组,而不是只给主进程设置一个堆大小。

我的选择是先分清“希望降低压力”和“必须控制失控范围”,再决定数值。以下预算只是原创配置示例,没有进行本机测试,也不代表适合所有服务的容量建议。

官方说明:https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html。内核接口:https://docs.kernel.org/admin-guide/cgroup-v2.html。管理命令:https://www.freedesktop.org/software/systemd/man/latest/systemctl.html。

使用场景:限制整个服务的资源范围

一个后台任务可能有主进程、子工作进程和共同使用的缓存。只看主进程 RSS,很容易漏掉同一服务资源组里的其他开销。用 resource-control 建立的是组级资源边界:服务在组内的进程及其受该层级约束的后代共同面对预算。

本文用已存在的 demo-worker.service 代指待调整服务,不提供假的可执行程序。请先把示例名换成真实单元。若实际应用由容器平台管理,应该确认该平台负责哪层 cgroup,避免只改宿主机一个无关服务。

候选方案:回收阈值、硬上限与 swap 上限

选项主要作用取舍
MemoryHigh超过阈值时施加回收和节流压力适合主要调控,但不保证绝不超出
MemoryMax约束组内内存,无法降低时可能组内 OOM适合最后防线,需接受失败风险
MemorySwapMax单独限制该组 swap 用量不是内存与 swap 相加的总额

MemoryHigh 本身不是到点杀进程的开关,过低会让服务花更多时间回收内存,表现为延迟或吞吐退化。MemoryMax 也不是应用能放心使用的“可用堆额度”;在特定情况下用量可短暂越过限制,无法回收时则可能触发组内 OOM。

因此三项可以配合使用,不能简单选一个“更强”的选项替代其他职责。MemorySwapMax=0 表达不允许该组使用 swap 的约束,但不自动解决工作集过大的问题;可回收空间减少后,容量策略仍需重新评估。

图1:服务资源组与三项内存控制接口的静态映射
图1:服务资源组与三项内存控制接口的静态映射。静态结构说明图,不是运行截图或性能结果。

对比维度:看 cgroup 统计而非单个进程

观察时区分当前组内存、swap 和事件计数。memory.current 包含被计入该组的多种内存开销,不能直接等同于单个进程的 RSS;memory.swap.current 则是另一项统计。应用自己报告的堆大小,也只是内部视角。

memory.events 中的 high、max、oom、oom_kill 有不同含义。high 增长体现触及高阈值的压力,不能据此断言发生了杀进程;oom_kill 才涉及 OOM 杀进程事件。应结合日志与业务失败情况判断,不把单个计数当成完整故障结论。

# 读取该服务实际的 cgroup 路径;本段仅针对统一 cgroup v2 挂载。
service_group=$(systemctl show demo-worker.service -p ControlGroup --value)
# 空路径时停止,避免把根组统计误认为目标服务。
if [ -z "$service_group" ] || [ "$service_group" = / ]; then
    printf '%s\n' '服务没有可用的独立 cgroup 路径' >&2
    exit 1
fi
# 这些是读取操作:当前内存、swap 和内存事件计数。
cat "/sys/fs/cgroup$service_group/memory.current"
cat "/sys/fs/cgroup$service_group/memory.swap.current"
cat "/sys/fs/cgroup$service_group/memory.events"

命令假定目标服务存在且 cgroup v2 挂载在 /sys/fs/cgroup。若路径下没有相应控制文件,应核对部署的层级、控制器可用性和命名空间。本文不建议在不了解系统管理方式时直接改写这些内核文件。

推荐选择:为现有服务添加专用 drop-in

为已有服务创建独立的 /etc/systemd/system/demo-worker.service.d/60-memory-budget.conf,避免直接编辑发行版提供的主单元文件:

[Service]
# 开启服务内存记账,便于按服务观察。
MemoryAccounting=yes
# 示例阈值:先以回收和节流施加压力。
MemoryHigh=640M
# 示例兜底:无法回收至范围内时,组内可能发生 OOM。
MemoryMax=896M
# swap 单独限制;这个示例最多允许 128 MiB。
MemorySwapMax=128M

示例让 MemoryHigh 低于 MemoryMax,给回收与业务缓冲保留区间。M 使用 1024 为基数;640M、896M 和 128M 均为演示值。真实数值应覆盖正常高峰、子进程并发、运行时堆外分配及业务允许的延迟。

# demo-worker.service 代表你已有的服务,先换成真实单元名。
sudo install -d /etc/systemd/system/demo-worker.service.d
# 把正文配置保存为该目录下的 60-memory-budget.conf,再重读定义。
sudo systemctl daemon-reload
# 在允许中断的维护窗口重启,让配置随服务启动应用。
sudo systemctl restart demo-worker.service
# 查看管理器配置值及当前记账值,数值以自己的环境为准。
systemctl show demo-worker.service -p MemoryAccounting -p MemoryCurrent -p MemoryHigh -p MemoryMax -p MemorySwapMax -p ControlGroup

这些命令是供读者在其 Linux 主机操作的步骤,本文没有执行。重启前安排维护窗口;部署后既要看配置值,也要看业务行为。设置一个阈值不会自动优化应用的数据结构,更不能代替修复持续增长的内存泄漏。

图2:服务主进程、工作进程与父 slice 的资源层级关系
图2:服务主进程、工作进程与父 slice 的资源层级关系。静态结构说明图,不是运行截图或性能结果。

不适用情况:层级限制和应用自身预算

如果父 slice 已经设置更严格的限制,子服务写一个更大的 MemoryMax 不能突破父级边界。systemd 的有效内存限制要考虑自身和祖先配置;只查看一个 drop-in 的字面值,无法解释全部可用容量。

同样,MemoryMax 不会修改 JVM、Python 扩展或其他运行时的内部预算。应用必须在组级总额里为堆、线程栈、原生分配和相关缓存预留空间。把堆上限直接等于组级硬上限,常会让辅助开销没有余地。

启动阶段和稳态需求不同也应单独考虑,但不要在缺乏负载证据时叠加大量参数。先理解当前系统支持的选项与控制器,再确定是否需要不同阶段的预算。本文不承诺给出一个跨发行版、跨业务的固定数值。

决策表与常见问题

实际需求选择思路
希望超出后先回收、容忍一定波动以 MemoryHigh 为主要调控
需要限制单服务失控影响增加有余量的 MemoryMax
需要控制交换占用单独配置 MemorySwapMax
多个服务共享总预算同时规划父 slice 和子服务约束
应用工作集持续增长保留边界,并修复应用原因

MemoryAccounting=yes 等于限制了吗?

不是。记账用于观察,限额仍由相应控制项表达。看到统计值不代表已经有合理的高阈值或硬上限。

服务没退出,是否说明阈值未生效?

不能这样判断。MemoryHigh 主要表现为回收压力和节流;硬限制触及后是否发生 OOM,也取决于能否回收以及具体分配。需要结合组统计、事件与日志。

参数会控制每个进程相同大小吗?

这里讨论的是资源组共享预算,不能把 896M 理解为每个工作进程各自可以使用 896M。并发增加时要考虑总工作集。

如何撤回示例配置?

只移除本次创建的 60-memory-budget.conf,再重读单元定义并在维护窗口按计划重启。不要为撤回一个内存设置而删除整个 drop-in 目录,里面可能还有其他运维配置;撤回后仍受父级资源约束。

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