服务器内存治理 - 从内存与日俱增到稳定运行 - 到底是哪里泄漏的内存
From Memory Creep to Stability: A Deep Dive into Server Memory Leaks
背景
吾有一小服务器,大二时写的服务一直在上面运行。后续增增改改,转眼间五年过去。
之前服务器可能平均大约能运行个一百五十天,然后内存就会疯狂报警,再过2天左右会因内存占满而死机。
最近这种现象开始变得频繁,十来天甚至五天都可能遇到一次。是时候治理一下了。
内存监控
所有服务刚启动时,服务器资源总占用不到1G,光服务运行缓存之类的正常不至于增到内存占满,大概是存在着一些内存泄漏或者死进程。
于是思路是写个脚本监控下悄悄增加的内存有哪些。
1 2 3 4 5 6
| TS=$(date +"%Y-%m-%d %H:%M:%S") OUT="/var/log/let_proc_snapshot.log"
echo "===== $TS =====" >> "$OUT" ps -eo pid,ppid,user,lstart,etimes,rss,comm,args --sort=-rss,ppid,pid >> "$OUT" echo >> "$OUT"
|
保存为WatchMem.sh,然后
设置为定时任务,1分钟采样一次(其实不用这么频繁就行)
添加一行:
1
| * * * * * /bin/bash -c '/xx/TFpath/WatchMem.sh'
|
会得到类似这样的日志:
1 2
| PID PPID USER STARTED ELAPSED RSS COMMAND COMMAND 2020 1 mysql Fri Dec 19 13:37:47 2025 250527 495552 mysqld /usr/libexec/mysqld --basedir=/usr
|
不得不说服务器这次挺给力的,刚运行不到48h内存就增加了200M,是时候分析下了。
日志采样
决定直接丢给ChatGPT让帮忙分析一波,结果30多MB的纯文本数据过大,无法上传。
于是写了个日志采样脚本,只保留分钟数是5的倍数的日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import re from typing import Optional from datetime import datetime
INPUT_FILE = "xx/let_proc_snapshot.log" OUTPUT_FILE = "xx/proc_snapshot_5min.log"
HEADER_RE = re.compile(r"^===== (.+) =====$")
def parse_time(line: str) -> Optional[datetime]: m = HEADER_RE.match(line.strip()) if not m: return None return datetime.strptime(m.group(1), "%Y-%m-%d %H:%M:%S")
def should_keep(ts: datetime) -> bool: return ts.minute % 5 == 0
def main(): with open(INPUT_FILE, "r", encoding="utf-8") as fin, \ open(OUTPUT_FILE, "w", encoding="utf-8") as fout:
block_lines = [] block_time = None
for line in fin: ts = parse_time(line) if ts: if block_time and should_keep(block_time): fout.writelines(block_lines)
block_lines = [line] block_time = ts else: block_lines.append(line)
if block_time and should_keep(block_time): fout.writelines(block_lines)
print(f"Resample done: {OUTPUT_FILE}")
if __name__ == "__main__": main()
|
文件体积变成了6M。
找到“元凶”
可能是AI的节能策略,GPT默认并没有分析日志文件,而是直接开始“猜测”可能原因。经过一轮对话的周折,AI一下就分析出来了。
进程中多了好几个python manage.py crontab run xxxxx,诶,说明很多定时任务没有退出。
一个python定时任务大约占用小50M内存,几个就占200M了。回到django目录运行python manage.py crontab show得知这个定时任务是我本科时候担任学校ACM集训队队长时,写的企业微信比赛提醒机器人。
脚本定时爬取比赛数据并在比赛即将开始时推送,比赛范围包括Codeforces、AtCoder等。
本科毕业已经几年过去,不知当时的机器人是否还有人在使用。但一看就能知道大概是外国这些网站HTTP请求阻塞了。
解决“元凶”
先杀掉所有被卡死的进程:
1
| pkill -f "manage.py crontab run"
|
然后设置crontab最大执行时间:
在*/5 * * * * timeout 60 python crontab run xxx命令前面加个timeout 60变成*/5 * * * * timeout 60 python crontab run xxx,这样超时也会自动退出了。
不看不知道,一看吓一跳
看了看才知道,服务器中依然使用的几年前的命令,不适合用在开发环境的:
1
| python manage.py runserver
|
赶紧改成了:
1 2
| gunicorn ProjectName.wsgi:application --workers 2 --bind 127.0.0.1:8000 --max-requests 1000 --max-requests-jitter 100
|
End
至此,本次治理暂时完结。
The Real End, Thanks!
同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~
千篇源码题解已开源