Python 服务内存缓慢增长排查手册
一、确认现象:是缓慢增长,还是正常波动
先不要一上来就怀疑泄漏。Python 服务内存缓慢增长有三种常见情形:第一,业务量上升导致缓存、连接池自然膨胀;第二,进程在处理高峰后没有回落;第三,真正的对象泄漏。区分方法是连续观察至少一个业务周期,例如 24 小时,记录 RSS 曲线是否单调上升、重启后是否重新爬升。
ps -o pid,rss,vsz,etime,cmd -p <PID>
# 每 10 分钟采样一次,写入日志后画曲线
while true; do date '+%F %T'; ps -o rss= -p <PID>; sleep 600; done
二、区分 cached 与泄漏
Linux 的 free 输出里 cached/buffers 是内核可回收内存,不能直接等同于进程泄漏。判断重点是进程自身的 RSS 和 Python 堆内对象数是否同步增长。先看系统层:
free -m
top -p <PID>
cat /proc/<PID>/status | grep -E 'VmRSS|VmSize'
如果 RSS 持续上升,而系统 cached 基本稳定,问题大概率在进程内;如果只是 cached 上升、可用内存仍充足,通常不必处理。
三、常用工具:从外到内
- top/ps:确认是哪个进程、哪个线程在增长。top 按 M 排序看内存,ps 看启动时间和 RSS 变化。
- pmap:看匿名内存段是否不断变大,区分堆、共享库和映射文件。
pmap -x <PID> | sort -k3 -nr | head -20
- py-spy:不重启服务也能看热点栈,判断是否卡在某个循环或批量处理里。
- tracemalloc:Python 内置工具,适合定位 Python 对象层面的增长点。
import tracemalloc
tracemalloc.start(25)
# 业务运行一段时间后
snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics('lineno')[:10]:
print(stat)
四、定位泄漏点:对照前后快照
只看一次快照容易误判。正确做法是取两个时间点的快照并做差分,重点看增长最快的类型和调用栈。常见泄漏源包括:全局字典或列表只增不删、缓存没有 TTL 和上限、事件监听器未注销、闭包长期持有大对象、日志上下文把请求对象挂在全局变量上。
import tracemalloc
tracemalloc.start(25)
before = tracemalloc.take_snapshot()
# ... 执行业务 ...
after = tracemalloc.take_snapshot()
for stat in after.compare_to(before, 'lineno')[:10]:
print(stat)
如果增长来自第三方库,先用最小复现脚本隔离:只调用可疑接口循环 10000 次,看 RSS 是否同步增长。
五、修复与验证
修复原则是给增长加边界:缓存用 LRU 并设 maxsize 和 TTL,批量任务分批处理并及时释放大对象,长连接按需关闭,定时任务避免把结果累积在内存里。修复后不要只看几分钟,要跑满一个业务周期,对比修复前后的 RSS 曲线和 tracemalloc 差分。
from functools import lru_cache
@lru_cache(maxsize=1024)
def get_config(key):
return load_config(key)
上线前再做一次压测:相同请求量下,服务内存应在高峰后回落到基线附近,差分快照里不再出现无界增长的调用栈,才算真正修好。
- 点赞
- 收藏
- 关注作者
评论(0)