如何调试陷入BUSY状态且占用内存的uWSGI Worker?
调试uWSGI/Django Worker僵死问题的实用方法
这种worker突然进入BUSY状态、耗尽内存导致实例崩溃的情况,在生产环境里确实让人头疼。结合我自己的实战经验,分享几个快速且规范的调试思路,帮你定位到具体的请求和函数:
1. 实时抓取BUSY Worker的当前请求
当你通过uwsgitop发现异常worker时,立刻用uWSGI自带的工具查看它正在处理的请求,这是最直接的方法:
- 如果你的uWSGI用的是unix socket,执行:
uwsgi --connect-and-read /path/to/your/uwsgi/socket
- 如果是TCP端口监听,执行:
uwsgi --connect-and-read 127.0.0.1:3031
这个命令会直接输出该worker当前处理的请求详情,包括HTTP方法、请求路径,甚至部分参数。结合Django的路由配置,你能快速对应到具体的视图函数。
2. 开启uWSGI请求追踪日志
修改你的uWSGI配置文件,添加日志增强参数,让它记录每个请求的完整生命周期,包括worker ID、处理时长、内存占用:
# 自定义日志格式,包含关键信息 log-format = %(addr) - %(user) [%(ltime)] "%(method) %(uri) %(proto)" %(status) %(size) %(micros)s %(memuse)B worker:%(worker_id) # 开启请求级别的详细日志 log-req = true
重启uWSGI后,日志会清晰记录每个worker处理的所有请求。当异常发生时,你可以根据worker ID和时间范围,筛选出它崩溃前处理的最后几个请求,缩小排查范围。
3. 实时追踪函数调用栈(用pyrasite/gdb)
如果worker已经僵死但还没崩溃,你可以附加到进程上查看当前的函数调用栈:
- 先找到异常worker的PID:
ps aux | grep uwsgi
- 安装
pyrasite后,执行命令进入该进程的交互式Python shell:
pyrasite <worker_pid>
- 在shell里打印当前的调用栈:
import traceback import sys # 获取所有线程的调用栈 frames = sys._current_frames() for thread_id, frame in frames.items(): print(f"Thread {thread_id}:") traceback.print_stack(frame)
这样你就能看到worker卡在哪个函数里,是数据库查询、外部API调用还是某个业务逻辑导致的问题。
4. 事后分析:生成core dump排查内存问题
如果worker已经崩溃,你可以提前配置uWSGI生成core dump文件,事后用gdb分析:
- 在uWSGI配置里添加:
enable-corefile = true corefile-path = /var/uwsgi/core-dumps/
- 当worker崩溃后,用gdb加载core文件:
gdb /path/to/uwsgi /var/uwsgi/core-dumps/core.<pid>
- 在gdb中执行
py-bt命令,就能看到Python层面的调用栈,定位到引发内存泄漏或死循环的函数。
5. Django层面添加请求监控中间件
在Django里自定义一个中间件,记录每个请求的视图函数、处理时长和内存变化:
import time import os import psutil from django.utils.deprecation import MiddlewareMixin class RequestMonitorMiddleware(MiddlewareMixin): def process_view(self, request, view_func, view_args, view_kwargs): request.start_time = time.time() request.start_mem = psutil.Process(os.getpid()).memory_info().rss request.view_func = view_func.__name__ def process_response(self, request, response): if hasattr(request, 'start_time'): duration = round(time.time() - request.start_time, 2) end_mem = psutil.Process(os.getpid()).memory_info().rss mem_diff = round((end_mem - request.start_mem) / 1024 / 1024, 2) # 可以将日志写入专门的文件或监控系统 print(f"Worker {os.getpid()} | View: {request.view_func} | Duration: {duration}s | Mem Change: {mem_diff}MB") return response
把这个中间件添加到Django的MIDDLEWARE配置中,这样你就能在日志里看到每个视图的内存消耗情况,快速定位到内存暴涨的业务函数。
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

