有一种故障特别难查:服务莫名其妙消失了,应用日志里什么都没留下,没有崩溃堆栈,没有异常退出记录。进程就这么凭空不见。碰到这种情况,十有八九是内核干的——内存不够了,OOM Killer 挑中它杀了。
OOM 的全称是 Out of Memory,它是内核的最后手段。Linux 默认采用乐观的内存分配策略,进程申请内存时内核只是答应下来,并不真的预留物理内存。等到所有进程都真要用内存了,发现不够,内核没别的办法,只能挑一个进程杀掉来腾地方。杀谁由一套打分机制决定。
这套打分有个反直觉的地方:它不保证杀掉占用最大的那个。评分会考虑进程的内存占用、运行时长、是不是 root 启动的、以及管理员设定的 oom_score_adj。我见过 sshd 被杀而失控的 Java 进程安然无恙的情况——因为后者被设过保护值。所以别想当然,去看日志。
确认是不是 OOM 的手段就一条:查内核日志。dmesg 里会有明明白白的一行,写着 Out of memory: Kill process,后面跟着进程号、进程名、评分,再后面是被杀进程当时占了多少内存。有这一行,案子就破了。
#!/bin/bash
# OOM 排查与关键进程保护
echo "=== 1. 确认是不是 OOM 杀的 ==="
dmesg -T 2>/dev/null | grep -i "killed process" | tail -n 10
journalctl -k 2>/dev/null | grep -i "out of memory" | tail -n 10
echo "=== 2. 看当前各进程的 OOM 评分,谁最危险 ==="
ps -eo pid,comm,oom,oomadj,rss --sort=-oom 2>/dev/null | head -n 15
echo "=== 3. 保护关键进程:sshd / mysqld / nginx ==="
for svc in sshd mysqld nginx; do
pid=$(pidof -s "$svc" 2>/dev/null)
if [ -n "$pid" ]; then
echo -500 > "/proc/$pid/oom_score_adj" 2>/dev/null \
&& echo "已保护 $svc (pid=$pid) oom_score_adj=-500"
fi
done
oom_score_adj 的取值范围是 -1000 到 1000。设成 -1000 等于告诉内核这个进程永远别杀,设成 1000 等于主动报名当炮灰。关键服务给负值,可牺牲的后台任务给正值,让内核在关键时刻有得选,不至于把数据库和 SSH 一起端掉。
# 持久化保护:写进 systemd 单元,别用命令行(重启就丢)
# 方式一:直接改服务文件
# /etc/systemd/system/mysqld.service.d/oom.conf
[Service]
OOMScoreAdjust=-500
# 方式二:推荐用 drop-in,不动原始文件
# systemctl edit mysqld 然后写入同样的内容
# 更进一步的主动限制:给服务设内存上限
# 达到 MemoryHigh 会被限流,达到 MemoryMax 才被杀
# 比等系统级 OOM 精准得多
[Service]
MemoryHigh=2G
MemoryMax=2500M
# 改完执行:
# systemctl daemon-reload
# systemctl restart mysqld
有人说既然 OOM 这么坑,干脆把它关掉。千万别。关掉之后内存耗尽时内核没有退路,要么直接 panic,要么整个系统卡死不响应,比杀掉一个进程惨烈得多。正确的思路是让内核杀对人,而不是不让它杀。
更根本的解法是别让内存走到那一步。给容易失控的服务设 systemd 的 MemoryMax,让它在自己的笼子里先被限制住;适当加一点 swap 作为缓冲,给系统留出反应时间;真不够了就扩容。OOM 保护是最后一道保险,平时该做的是别让这道保险被触发。
相关阅读:OOM Kill 是系统层的最后一道防线。《服务器故障排查总纲:按现象反查的五层定位法》按现象反查 24 种故障,内存被谁吃掉、进程为什么消失,从总纲定层到本篇定位,一条线走完。
A5创业网 版权所有