先说结论:
目前已知的3种方法都有一定的局限性,不能很完美实现当进程内存泄漏时杀掉进程
1. systemd的服务配置中增加MemoryMax=XXX的配置项,该配置项仅能限住物理内存,如何服务器未配置swap,内存达到时则会触发oom杀掉进程。如果服务器配置swap,将继续消耗swap直到swap也消耗完,systemd还有一个MemorySwapMax=XXX配置项可以限制swap,但是除非很新的操作系统使用了cgroup2该配置项才能生效。
2. systemd配置的MemoryMax=XXX参数,会同时作用到被主进程拉起的其他进程,如主进程拉起mysql进程,虽然systemd的MemoryMax参数是作用在主进程上,但是mysql会继承该限制。原因是MemoryMax参数是通过cgroup生效的,systemd启动的主进程和子进程都将在该cgroup组中。
3. ulimit和prlimit设置进程最大内存地址,这两种方式是作用在系统函数行为上,比如malloc分配内存时被限制了内存上限,内存分配不出来的时候panic,但是如果上层调用捕获了这个异常,程序就不会退出了,并不会处罚oom杀掉进程。
实验过程:
方式一.通过在systemd服务中配置MemoryMax:
当前机器cgroup为v1,若是v2将显示为cgroup2fs
[root@test ~]# stat -fc %T /sys/fs/cgroup
tmpfs
测试脚本
该脚本使用python构造一个很大的内存,触发方式是在/root下创建start文件,touch /root/start
,用其他语言可以类似
# cat test.py
import os
while True:
if os.path.exists("/root/start"):
b = []
for i in range(1000000000):
b.append(i)
创建服务脚本
[root@test ~]# cat /usr/lib/systemd/system/mem-test.service
[Unit]
Description=mem-test
[Service]
MemoryMax=512M
MemorySwapMax=0
ExecStart=/usr/bin/python /root/test.py
[Install]
WantedBy=multi-user.target
启动服务
[root@test ~]# systemctl daemon-reload
[root@test ~]# systemctl restart mem-test
[root@test ~]# systemctl status mem-test
● mem-test.service - mem-test
Loaded: loaded (/usr/lib/systemd/system/mem-test.service; disabled; vendor preset: disabled)
Active: active (running) since Wed 2025-02-26 09:10:14 CST; 3s ago
Main PID: 3485 (python)
Tasks: 1 (limit: 47360)
Memory: 3.9M (max: 512.0M swap max: 0B available: 508.0M)
CPU: 3.600s
CGroup: /system.slice/mem-test.service
└─ 3485 /usr/bin/python /root/test.py
Feb 26 09:10:14 test systemd[1]: Started mem-test.
结论
top中看到的RSS将达到MemoryMax上线后不再增长,若服务器未配置swap,则服务会立刻被oom-kill,若服务器配置swap,swap将增加直到消耗完后被oom-kill
注意:由于swap本质用的是存储资源,因此当swap快速增长时,实际是存储迅速有大量IO,会导致服务器有卡顿。
补充测试
当将服务器改为cgoupv2后,可以支持MemorySwapMax配置项,即使服务器配置了swap,当达到MemorySwapMax后会立刻oom-kill
若以下stat未显示cgroup2fs,说明1.grub配置失效,2.操作系统内核太低不支持
[root@test ~]# cat /proc/cmdline
BOOT_IMAGE=... crashkernel=512M systemd.unified_cgroup_hierarchy=1
[root@test ~]# stat -fc %T /sys/fs/cgroup
cgroup2fs
[root@test ~]# find /sys/fs/cgroup -name "mem-test*"
/sys/fs/cgroup/system.slice/mem-test.service
[root@test ~]# cat /sys/fs/cgroup/system.slice/mem-test.service/memory.swap.max
0
[root@test ~]# cat /sys/fs/cgroup/system.slice/mem-test.service/memory.max
536870912
方式二.通过在ulimit/prlimit限制进程
ulimit的单位为KB,prlimit单位为B
待进程内存超过限制后,将异常退出,退出原因是程序执行过程未捕获异常
[root@test ~]# ulimit -v 524288 && python test.py
Traceback (most recent call last):
File "/root/test.py", line 5, in <module>
for i in range(1000000000):
MemoryError
[root@test ~]# prlimit --rss=536870912:536870912 --as=536870912:536870912 python test.py
Traceback (most recent call last):
File "/root/test.py", line 5, in <module>
for i in range(1000000000):
MemoryError
修改代码增加异常捕获
[root@test ~]# cat test.py
import os
while True:
try:
if os.path.exists("/root/start"):
b = []
for i in range(1000000000):
b.append(i)
except:
pass
再次执行,进程将不能退出
[root@test ~]# ulimit -v 524288 && python test.py
err
err
err
err
err
...
[root@test ~]# prlimit --rss=536870912:536870912 --as=536870912:536870912 python test.py
err
err
err
err
err
...