Django 应用 OOM(Out of Memory)故障的定位思路和排查方法

作者:哈里谢顿日期:2026/2/24

二、定位思路总览

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
P1N+1 查询加 select_related/prefetch_related
P1大文件内存处理改为流式/chunk 处理
P2Worker 不重启配置 max_requests
P2序列化大对象用 values_list 减少字段


Django 应用 OOM(Out of Memory)故障的定位思路和排查方法》 是转载文章,点击查看原文


相关推荐


我又开发了一款桌面APP,功能强大
500佰2026/2/16

最近这段时间,开始沉迷一件事,在抖音录制我AI写代码、做实战开发的视频,用opencode / claudecode / Agent skills 等大模型进行AI项目开发,耗时7个晚上,最晚的一次,写到了夜间3点,录制了5个视频,开发消耗AI大模型token 数1500左右。 这次我开发了一款桌面录屏APP,名字叫做focusME,目前已经开发完成,可一键安装在我们的桌面,接下来讲解一下整个开发过程。 开发成果 开发过程 前面我用opencode里面Agent skills去制定产品


Skills.lc 是什么?为什么我会做(用)这个站
HBLOG2026/2/7

在折腾 AI Agent、CLI 工具和各种自动化脚本的过程中,我一直有一个很现实的问题: 好的 skill / workflow 到底该放哪?怎么复用? Prompt 太零散,放在 Notion、Gist、README 里,时间一长就找不到; 不同项目里反复复制粘贴,又很难维护; 看到 GitHub 上有人写了不错的 skill,也不知道怎么发现、怎么用。 Skills.lc 就是在这样的背景下出现的。 它本质上不是“又一个 AI 平台”,而是一个 技能索引与分发站点,专门用来收集、整理


Spring注解秘籍:优雅地使用 @RequestHeader
独泪了无痕2026/1/29

前言   在 Spring Boot 开发中,HTTP 请求头(Header)是客户端和服务器之间传递元数据的重要方式。通过请求头,客户端可以传递认证信息、内容类型、语言偏好等数据。Spring Boot 提供了 @RequestHeader 注解,用于方便地从 HTTP 请求头中提取数据。本文将详细介绍 @RequestHeader 注解的使用方法,包括基本用法、默认值处理、多值头处理以及实际应用场景。 一、注解定义与核心属性 1.1 @RequestHeader 是什么   在构建现代 W


筑牢金融底座:企业级区块链全球化数据库架构设计白皮书
China_Yanhy2026/1/20

📖 前言:Web3 业务的双重账本 在 Web3 业务中,区块链(AMB)是不可篡改的“链上真理”,而关系型数据库(RDS/Aurora)则是承载用户资产、撮合逻辑和KYC信息的“链下业务核心”。对于追求全球化的高频交易项目,数据库的架构设计必须解决两个核心矛盾:跨国访问的物理延迟 与 资金数据的一致性。 第一部分:旗舰方案 —— Amazon Aurora Global Database (深度解析) 这是针对跨国交易所(如币安、Coinbase 模式)的首选架构。 1. 核心架构


Ansible自动化(十五):加解密详解
cly12026/1/12

Ansible Vault 是 Ansible 提供的一套用于保护敏感数据的机制,可以对各类配置文件进行加密,防止敏感信息(如密码、私钥、API 密钥等)以明文形式暴露在代码仓库或配置文件中。 一、为什么需要 Ansible 加密? 场景说明: Playbook 中包含数据库密码、API Token、SSH 私钥等敏感信息Inventory(主机清单)中直接写入了连接密码(如 ansible_password)变量文件(vars/main.yml)中包含机密配置 ✅ Ansible Vaul


Go 项目结构总是写乱?这个 50 行代码的 Demo 教你标准姿势
Java小成2026/1/4

1. 场景复现:那个让我头疼的时刻 去年,我接手了一个"祖传" Go 项目。打开代码仓库的那一刻,我整个人都不好了——所有代码都塞在一个 main.go 里,足足 3000 多行。想加个功能?先花半小时找代码在哪。想写个单元测试?抱歉,函数全是私有的,而且互相耦合,根本没法单独测。 我当时就在想:如果当初写这个项目的人,能从第一天就用一个规范的结构,后面的人得少掉多少头发? 后来我开始研究 Go 官方和社区推荐的项目布局,发现其实规则很简单,但很多人就是不知道。于是我写了这个 50 行代码的小


Vue 实例挂载的过程是怎样的?
全栈陈序员2025/12/25

一、整体流程概览 当我们执行 new Vue({ ... }) 时,Vue 会经历 初始化 → 编译模板 → 挂载 DOM 三个阶段。整个过程由 _init 方法驱动,最终通过 $mount 完成视图渲染。 核心路径: new Vue() → _init() → initState() → $mount() → mountComponent() → _render() → _update() → 真实 DOM 二、详细步骤解析 1. 构造函数与 _init 初始化 源码位


从已损坏的备份中拯救数据
神奇的程序员2025/12/17

前言 12月15号早上,一觉醒来,拿起手机看到我的邮箱收到了内网服务无法访问的告警邮件,本以为只是简单的服务卡死,将服务器重启后就去上班了。 后来,陆续有好友联系我说网站挂了。 定位问题 晚上下班回家后,尝试将电脑断电重启,发现pve只能存活2分钟左右,然后整个系统卡死,无法进行任何操作。首先,我想到的是:会不会某个vm虚拟机或者ct容器影响到宿主机了。 因为系统只能存活几分钟,在执行禁用操作的时候,强制重启了好几次服务器。当所有的服务都停止启动后,卡死的问题依旧存在。 翻日志 没辙了,这已经


苹果ios手机ipad安装配置ish终端shell工具
无痕melody2025/12/9

简介 官方介绍 iSH 是一个运行在 iOS 上的 Linux Shell,用来在ARM架构的 iOS 设备上模拟 X86 架构。也就是说不光是 IPad 可以安装,IPhone 上也可以安装运行 iSH,直接在 IOS 设备上运行 Linux 环境,而且免费! 如果你正在使用的电脑是 Mac,那么可以把 iSH 比作你电脑上面的终端。 iSH 官方地址 安装 AppStore里搜索ish或手机打开链接 配置 基本操作 操作按钮 2. 这个按钮相当于电脑上的 Tab 键,用于命令


用户数据报协议(UDP)详解
CodePracticer2025/11/28

一、传输层协议UDP 1. 理解UDP协议 我们以前说过,0-1023端口号是知名端口号,它们是与指定的协议进行关联的,那么我们如何证明呢? 在指定目录下就可以查找到这些协议的端口号了(/etc/services)。 这里以两个例子来说明情况。 前面我们也说过协议就是一种约定,本质就是结构体。今天我们来正式认识一下UDP协议。 可以看到UDP协议的宽度是32位,源端口号和目的端口号分别占16位,UDP协议的报头是8字节。 前面我们说过,源主机的数据发送给目标主机需要先经历封装在解包的过程,

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客