服务器突然变卡,free 一看内存快被吃光,我顺着 ps 和 dmesg 揪出了真凶

举报
yd_242783495 发表于 2026/11/14 15:55:29 2026/11/14
【摘要】 一台 2G 内存的 ECS 下午突然卡得连 SSH 都要等半天,free 显示可用内存只剩几十兆。我先是被缓存数字唬住,又差点把共享内存重复算了两遍,最后在 dmesg 里翻到 OOM killer 的杀人记录,顺藤摸瓜找到一个凌晨跑起来的 Python 任务。把这趟排查和最后的几招治标治本记下来。

周三下午三点多,正在跑的一台 2G 内存 ECS 突然卡得不行。SSH 连上去要等十几秒,敲个 ls 都要顿一下,业务接口直接超时。同事在群里问是不是机器挂了,我先登录上去看了一眼:

free -h

输出大概是这样:

              total        used        free      shared  buff/cache   available
Mem:          1.9Gi       1.7Gi        46Mi        12Mi       180Mi       120Mi
Swap:            0B          0B          0B

available 只剩 120M,free 只有 46M。第一反应是内存被什么东西吃光了,但到底是谁吃的,还得一层层扒。

先被 free 的数字唬了一下

刚学 Linux 那会儿我看到 used 1.7G 就慌,其实这里面有个老坑:buff/cache 那 180M 是内核拿来做文件缓存的,内存紧张的时候它会自己吐出来,真正要看的是 available 那一列。available 120M 是真的告急了,不是缓存虚胖。

还有一个细节:这台机器 Swap 是 0。2G 内存的小机器没配 Swap,等于一点缓冲余地都没有,内存一满就只能靠 OOM killer 动手。

ps 排序,差点被共享内存骗了

接着找吃内存的大户:

ps aux --sort=-%mem | head -15

排第一的是个 Python 进程,RSS 900M 左右。第二名是个 Java 进程,RSS 600M 左右。两家加起来 1.5G,基本对得上。

但这里有个坑我以前踩过:ps 显示的 RSS 里包含共享内存,同一个共享库被十个进程映射,就会重复算十次。所以光看 RSS 排序,只能当线索,不能直接定罪。想看得准一点,得看 PSS(按比例分摊后的共享内存):

# smem 没装的话先装一个,Debian/Ubuntu
sudo apt install -y smem
smem -s pss -r | head -10

或者直接翻 /proc:

cat /proc/<pid>/status | grep -E 'VmRSS|VmSize|VmSwap'
grep Pss /proc/<pid>/smaps | awk '{s+=$2} END {print s " kB"}'

我翻了那个 Python 进程的 smaps,PSS 也有 800M 出头,共享内存那点水分影响不大——这家伙确实是真凶,不是被冤枉的。

dmesg 里找到了 OOM killer 的记录

正准备 kill 掉它之前,我想确认一下它到底在干什么,顺手翻了下内核日志:

dmesg -T | grep -i -A 5 'oom-killer\|out of memory'

还真有:

[Wed Nov 11 02:14:33 2026] python3 invoked oom-killer: gfp_mask=0x140cca, order=0
[Wed Nov 11 02:14:33 2026] Out of memory: Killed process 18432 (python3) total-vm:1823456kB

有意思,凌晨两点多 OOM killer 已经杀过一次这个 Python 进程了,早上它又被 systemd 拉起来,继续吃内存,下午终于把机器拖垮。也就是说这不是突发事件,是个反复发作的老毛病。

顺着进程的启动时间往回查:

ps -o pid,lstart,cmd -p <pid>
systemctl status <对应的服务名>

对应的 systemd 服务凌晨两点被拉起来过一次,服务文件里 Restart=always,被杀了就自动重启,重启了继续吃内存,等于一个死循环。更离谱的是,这个任务本来只是每天凌晨跑一次数据清洗的定时脚本,不知道什么时候被人改成了常驻服务。

揪出内存泄漏的那几行代码

把脚本翻出来看,问题一眼就看出来了:

# 问题代码:把全表数据一次性读进内存再处理
rows = cursor.fetchall()   # 表里 200 多万行
results = []
for r in rows:
    results.append(process(r))

200 多万行全塞进一个 list,再一份份处理,内存不爆才怪。而且这个表最近两个月涨得很快,以前数据量小的时候跑得好好的,没人发现这个写法有问题。

改成流式处理,改动其实很小:

# 改后:一次只取一批,处理完就扔
cursor.execute("SELECT ... FROM ...")
while True:
    batch = cursor.fetchmany(1000)
    if not batch:
        break
    for r in batch:
        process(r)

改完之后 RSS 从 900M 掉到 80M 左右。顺手还把 Restart=always 改成 Restart=on-failure,并且加了 RestartSec=60,避免被杀了立刻原地复活继续吃内存。

治标的两招:Swap 和 earlyoom

代码修了,但 2G 内存的机器还是太薄,我又补了两招兜底。

第一招是加 Swap。注意一个坑:用 fallocate 建出来的文件可能有空洞,mkswap 会直接报错,稳妥的办法是用 dd:

# 2G Swap,用 dd 写实数据,别用 fallocate
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

顺手把 vm.swappiness 调到 10,让内核不到万不得已别去碰 Swap:

echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

第二招是装 earlyoom,让它在内存耗尽之前先动手杀掉最肥的进程,别等内核的 OOM killer 在半夜乱杀:

sudo apt install -y earlyoom
sudo systemctl enable --now earlyoom

事后复盘,我记了三条

第一,free 要看 available,不是 used,缓存那部分是内核借去用的,不算真占用。第二,定位吃内存的进程别只看 RSS 排序,共享内存会重复算,PSS 或者 /proc/<pid>/smaps 更准,dmesg 里的 OOM 记录是最直接的证据。第三,Restart=always 配上有内存泄漏的程序,等于给机器装了个自动复活的炸弹,定时任务就老老实实用 cron 或者 Restart=on-failure。

这台机器现在跑了三天,内存稳在 600M 上下,available 没再掉下过 1G。2G 的小机器,代码写得省一点,照样能撑住。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。