二、定位思路总览
11. 确认现象 → 2. 内存分析 → 3. 代码审查 → 4. 复现验证 → 5. 修复优化 2 ↑___________________________________________________________| 3
三、详细排查步骤
第一步:确认内存使用趋势
1.1 系统层面监控
1# 查看进程内存(RSS:实际物理内存,VSZ:虚拟内存) 2ps aux --sort=-%mem | head -20 3 4# 实时观察 5watch -n 1 'ps -p <PID> -o pid,rss,vsz,comm' 6 7# 查看系统 OOM 日志 8dmesg -T | grep -i "killed process" 9grep "Out of memory" /var/log/syslog 10
1.2 Django/Gunicorn 层面
1# Gunicorn worker 内存 2pgrep -f gunicorn | xargs -I {} ps -p {} -o pid,rss,vsz,cmd 3 4# 查看 worker 重启次数(内存过高会被 master 重启) 5grep "Worker" /var/log/gunicorn/error.log 6
1.3 监控图表分析
- 内存持续增长不释放 → 内存泄漏
- 内存周期性波动 → 正常,但峰值过高
- 突然飙升后 OOM → 大对象分配/批量操作
第二步:Python 内存分析工具
2.1 基础工具:tracemalloc(Python 3.4+)
1# settings.py 中启用 2import tracemalloc 3 4tracemalloc.start(25) # 保留25个栈帧 5 6# 在需要查看的地方(如中间件、信号) 7import tracemalloc 8 9def log_memory(): 10 snapshot = tracemalloc.take_snapshot() 11 top_stats = snapshot.statistics('lineno')[:10] 12 13 for stat in top_stats: 14 print(f"{stat.size / 1024 / 1024:.1f} MB") 15 print(stat.traceback.format()[-3:]) 16
2.2 进阶工具:memory_profiler
1pip install memory_profiler 2
1from memory_profiler import profile 2 3@profile 4def heavy_view(request): 5 # 逐行显示内存变化 6 users = User.objects.all() # 危险! 7 data = list(users) # 内存爆炸点 8 return JsonResponse(data) 9
运行:
1python -m memory_profiler manage.py runscript test_script 2
2.3 对象级分析:pympler + objgraph
1from pympler import tracker, muppy, summary 2import objgraph 3 4# 跟踪对象增长 5tr = tracker.SummaryTracker() 6tr.print_diff() # 显示对象数量变化 7 8# 查看最占内存的对象 9all_objects = muppy.get_objects() 10sum1 = summary.summarize(all_objects) 11summary.print_(sum1) 12 13# 查找循环引用(Django ORM 常见) 14objgraph.show_most_common_types(limit=20) 15objgraph.show_backrefs([some_obj], max_depth=10) 16
2.4 生产环境安全工具:Django-Prometheus + 自定义指标
1# 暴露内存指标给 Prometheus 2import os 3import psutil 4 5process = psutil.Process(os.getpid()) 6 7def memory_usage_mb(): 8 return process.memory_info().rss / 1024 / 1024 9 10# 在关键路径记录 11logger.info(f"After query: {memory_usage_mb():.1f} MB") 12
第三步:Django 特有内存陷阱排查
3.1 ORM 查询优化(最常见!)
| 问题代码 | 内存影响 | 解决方案 |
|---|---|---|
| Model.objects.all() | 全表加载 | iterator() / values_list() |
| len(queryset) | 强制求值 | .count() |
| list(queryset) | 全部载入内存 | 分页处理 |
| select_related() 滥用 | JOIN 过大 | 只选必要字段 |
| N+1 查询 | 多次 DB 往返 | prefetch_related() |
危险代码示例:
1# ❌ 内存爆炸:100万用户 × 1KB = 1GB 2def bad_export(request): 3 users = User.objects.all() 4 data = [] 5 for user in users: # 这里才触发查询,全部载入内存 6 data.append({ 7 'name': user.name, 8 'orders': list(user.orders.all()) # N+1! 9 }) 10 return JsonResponse(data) 11 12# ✅ 流式处理 + 预加载 13def good_export(request): 14 users = User.objects.prefetch_related('orders').iterator(chunk_size=1000) 15 response = StreamingHttpResponse( 16 (json.dumps({'name': u.name}) for u in users), 17 content_type='application/json' 18 ) 19 return response 20
3.2 检查 QuerySet 缓存
1# Django Debug Toolbar 或手动检查 2from django.db import connection 3 4def show_queries(): 5 print(f"Query count: {len(connection.queries)}") 6 for q in connection.queries[-5:]: 7 print(q['sql'][:100], f"{q['time']}s") 8
3.3 中间件/信号泄漏
1# 检查是否有全局变量累积数据 2_BAD_CACHE = [] # 危险!进程级全局变量 3 4class BadMiddleware: 5 def __init__(self, get_response): 6 self.get_response = get_response 7 8 def __call__(self, request): 9 _BAD_CACHE.append(request.user) # 只增不减! 10 return self.get_response(request) 11
3.4 文件上传处理
1# ❌ 大文件直接读入内存 2def bad_upload(request): 3 content = request.FILES['file'].read() # 100MB 文件 = 100MB 内存 4 5# ✅ 流式处理 6def good_upload(request): 7 for chunk in request.FILES['file'].chunks(chunk_size=8192): 8 process_chunk(chunk) 9
第四步:Gunicorn/Uvicorn 配置优化
1# gunicorn.conf.py 2import multiprocessing 3 4# Worker 类型 5worker_class = "sync" # 或 "gevent" / "uvicorn.workers.UvicornWorker" 6 7# 关键:限制 worker 数量,防止内存耗尽 8workers = multiprocessing.cpu_count() * 2 + 1 9max_requests = 1000 # 处理1000请求后重启 worker(防泄漏) 10max_requests_jitter = 50 # 随机抖动,避免同时重启 11timeout = 30 12 13# 内存限制(需要 systemd/cgroups 配合) 14# 或使用 --worker-tmp-dir /dev/shm 15
K8s 配置:
1resources: 2 limits: 3 memory: "512Mi" 4 requests: 5 memory: "256Mi" 6
第五步:诊断流程图
1发现 OOM 2 │ 3 ├─► 查看监控:是持续增长还是突然飙升? 4 │ │ 5 │ ├─ 持续增长 ──► 内存泄漏嫌疑 6 │ │ ├─ 用 tracemalloc 对比快照 7 │ │ └─ 检查全局变量、单例、缓存 8 │ │ 9 │ └─ 突然飙升 ──► 大对象分配嫌疑 10 │ ├─ 检查批量查询/导出功能 11 │ ├─ 检查文件上传处理 12 │ └─ 检查大数据量序列化 13 │ 14 └─► 复现问题 15 │ 16 ├─ 本地用 memory_profiler 定位具体行 17 │ 18 └─ 生产用日志打点 + 采样分析 19
四、快速检查清单
1# 1. 立即查看当前内存大户 2ps aux --sort=-%mem | head -10 3 4# 2. 查看 Django 进程 5pgrep -f "gunicorn\|python" | xargs ps -o pid,rss,vsz,cmd 6 7# 3. 检查是否有明显泄漏模式(RSS 持续增长) 8for i in {1..10}; do 9 ps -p <PID> -o rss= >> memory.log 10 sleep 5 11done 12 13# 4. 分析 Python 对象(如果还能进入 shell) 14python -c " 15import sys, gc 16print(f'GC objects: {len(gc.get_objects())}') 17big = [o for o in gc.get_objects() if sys.getsizeof(o) > 1000000] 18print(f'Objects > 1MB: {len(big)}') 19for o in big[:5]: 20 print(type(o), sys.getsizeof(o)) 21" 22
五、修复优先级
| 优先级 | 问题 | 修复方式 |
|---|---|---|
| P0 | 无分页全表查询 | 加 .iterator() 或分页 |
| P0 | 全局变量累积 | 改为 Redis/Memcached |
| P1 | N+1 查询 | 加 select_related/prefetch_related |
| P1 | 大文件内存处理 | 改为流式/chunk 处理 |
| P2 | Worker 不重启 | 配置 max_requests |
| P2 | 序列化大对象 | 用 values_list 减少字段 |