背景

某台小型 Linux 主机上运行着一个 AI 代理网关服务。该服务平时占用并不高,但在运行一段时间后,systemctl status 显示历史内存峰值曾达到约 5.5GB

状态中可以看到类似结构:

某网关服务
├─ node 主进程
├─ node 子进程
└─ app-server / worker 子进程

这说明服务并不是单一进程,而是由主进程和若干子进程共同组成。若只限制某个 Node 进程,无法可靠覆盖整个服务组。更合理的方式是使用 systemd cgroup 资源限制,对整个 service 统一设置内存上限。

问题

该主机总内存约为 8GB,且没有启用 swap。AI 网关服务如果在高负载或异常情况下继续增长内存占用,可能导致系统进入异常状态,例如:

  • SSH 连接断开;
  • 系统响应变慢;
  • 其他服务被挤压;
  • 最严重时出现假死或需要手动重启。

因此,需要给该服务设置一个最大内存使用上限。

目标不是修改应用源码,也不是给应用打补丁,而是通过系统服务管理器限制资源使用。

方案选择

最终采用的是 systemd 用户级 service drop-in 配置

这种方式的特点是:

  • 不修改应用源码;
  • 不修改 npm / node_modules 内部文件;
  • 不直接编辑主 unit 文件;
  • 使用 systemd 官方支持的 drop-in 覆盖机制;
  • 应用升级后通常不会覆盖该配置;
  • 限制范围覆盖整个 service cgroup,包括主进程和子进程。

也就是说,这不是“打补丁”,而是系统层面的标准资源控制。

配置目标

最终设置为:

MemoryAccounting=yes
MemoryHigh=5G
MemoryMax=6G
OOMPolicy=stop
Restart=on-failure
RestartSec=10s

含义如下:

MemoryAccounting=yes

启用内存统计,让 systemd 记录该服务的内存使用情况。

MemoryHigh=5G

软限制。服务内存超过 5GB 后,systemd 会开始施压、回收、限速,尽量避免继续膨胀。

MemoryMax=6G

硬限制。整个服务 cgroup 的内存占用不能超过 6GB。主进程和子进程合计计算。

OOMPolicy=stop

如果服务因超出内存限制触发 OOM,systemd 会停止整个服务,而不是让部分子进程处于不一致状态。

Restart=on-failure
RestartSec=10s

如果服务异常退出或因内存限制失败,10 秒后自动重启。

配置步骤

以下命令使用占位符表示服务名,实际操作时应替换为真实 service 名称。

SERVICE_NAME="<gateway-service>.service"

mkdir -p "$HOME/.config/systemd/user/${SERVICE_NAME}.d"

cat > "$HOME/.config/systemd/user/${SERVICE_NAME}.d/20-memory-limit.conf" <<'EOF'
[Service]
MemoryAccounting=yes
MemoryHigh=5G
MemoryMax=6G
OOMPolicy=stop
Restart=on-failure
RestartSec=10s
EOF

systemctl --user daemon-reload
systemctl --user restart "$SERVICE_NAME"

这里使用的是用户级 systemd 服务,因此命令为:

systemctl --user ...

如果是系统级服务,则路径和命令需要改为系统级 unit 的对应形式。

验证配置是否生效

执行:

systemctl --user show "$SERVICE_NAME" \
-p MemoryAccounting \
-p MemoryCurrent \
-p MemoryPeak \
-p MemoryHigh \
-p MemoryMax \
-p OOMPolicy \
-p Restart \
-p RestartUSec

确认输出中出现类似结果:

Restart=on-failure
RestartUSec=10s
OOMPolicy=stop
MemoryAccounting=yes
MemoryHigh=5368709120
MemoryMax=6442450944

这里的数值换算如下:

5368709120  = 5 GiB
6442450944 = 6 GiB

再查看服务状态:

systemctl --user status "$SERVICE_NAME" --no-pager

如果状态中出现类似内容:

Drop-In: .../<gateway-service>.service.d
└─20-memory-limit.conf

Memory: 542.6M (high: 5.0G max: 6.0G ...)

就说明 drop-in 配置已经被 systemd 加载,并且内存限制已经生效。

结果

配置完成后,服务状态显示:

high: 5.0G
max: 6.0G

这说明该网关服务已经被限制为:

  • 当前占用:几百 MB 级别;
  • 软限制:5GB;
  • 硬上限:6GB;
  • 超限后停止服务;
  • 失败后自动重启。

由于限制作用于整个 service cgroup,后续即使服务启动额外的 worker、app-server 或子进程,也会被一起纳入 6GB 上限内。

相关的心跳配置

同一服务此前还调整过 heartbeat 频率,将默认高频心跳改为较低频率:

heartbeat every = 6h

验证状态中可以看到:

"heartbeat": {
"enabled": true,
"every": "6h",
"everyMs": 21600000
}

这说明服务不再以过高频率发送心跳事件,有助于减少不必要的任务触发和系统负担。

总结

这次处理的核心不是修复应用本身,而是给长期运行的 AI 网关服务加上一道系统级安全边界。

最终形成了两层稳定性控制:

心跳频率:6 小时一次
内存软限制:5GB
内存硬上限:6GB
异常后:停止并自动重启

对于资源有限的小型 Linux 主机,这种做法比单纯依赖应用自我控制更稳妥。应用可以继续正常运行,但一旦内存异常增长,systemd 会在系统被拖垮之前介入处理。

这种方式干净、正式、可维护,不属于源码补丁,也不会污染应用安装目录。

Leave a Reply

Your email address will not be published. Required fields are marked *