Gunicorn与Django:请求超时自定义错误抛出方案咨询
Django层面主动捕获请求超时并抛出自定义TimeoutError的最优方案
核心思路
在Django请求处理流程中,设置一个略短于Gunicorn超时时间的请求超时阈值(比如Gunicorn设20秒,Django设18秒),当请求处理耗时达到该阈值时,主动抛出自定义TimeoutError,确保监控工具(Datadog/Sentry)能完整捕获错误上下文,避免Gunicorn发送SigAbt杀死进程导致的追踪中断。
实现方案:自定义Django中间件
中间件是Django中拦截所有请求的最佳入口,通过系统信号实现超时触发,无需额外依赖:
1. 定义自定义超时异常
在项目的exceptions.py中添加:
class TimeoutError(Exception): """自定义请求超时异常""" pass
2. 编写超时处理中间件
在项目的middleware.py中实现中间件:
import signal from django.http import JsonResponse from .exceptions import TimeoutError class RequestTimeoutMiddleware: def __init__(self, get_response): self.get_response = get_response # 设置超时时间(比Gunicorn的timeout小2秒,避免冲突) self.timeout_seconds = 18 def __call__(self, request): # 定义超时信号处理函数 def handle_timeout(signum, frame): raise TimeoutError("请求处理超时,已主动终止") # 注册SIGALRM信号及处理函数 signal.signal(signal.SIGALRM, handle_timeout) # 设置超时闹钟 signal.alarm(self.timeout_seconds) try: response = self.get_response(request) except TimeoutError as e: # 捕获自定义超时异常,返回标准错误响应 response = JsonResponse({"error": str(e)}, status=504) # 触发Sentry/Datadog的错误上报(如果已集成) raise e finally: # 请求处理完成,取消闹钟 signal.alarm(0) return response
3. 激活中间件
在settings.py的MIDDLEWARE列表中添加该中间件(建议放在靠前位置,确保所有请求都被拦截):
MIDDLEWARE = [ 'your_project.middleware.RequestTimeoutMiddleware', # 其他中间件... 'django.middleware.common.CommonMiddleware', # ... ]
4. 调整Gunicorn配置
确保Gunicorn的--timeout值大于Django中间件设置的超时时间,保持原配置即可:
gunicorn config.wsgi --timeout 20 (...)
关键注意事项
- 信号兼容性:
SIGALRM仅在类Unix系统(如Linux)中可用,符合EKS集群的运行环境。 - 多进程安全:Gunicorn的每个worker是独立进程,信号注册仅作用于当前worker进程,不会跨进程干扰。
- 异常上报:捕获
TimeoutError后重新抛出,确保已集成的监控工具能正常捕获并记录错误堆栈。 - 边缘场景处理:在
finally块中取消闹钟,避免请求正常完成后仍触发超时信号。
可选优化方案
如果需要更精细的超时控制(比如针对特定视图设置不同超时),可以扩展中间件,通过视图装饰器传递自定义超时值:
# 视图装饰器 def timeout(seconds): def decorator(view_func): def _wrapped_view(request, *args, **kwargs): request.timeout_seconds = seconds return view_func(request, *args, **kwargs) return _wrapped_view return decorator # 修改中间件的__call__方法,优先使用视图设置的超时 def __call__(self, request): timeout_seconds = getattr(request, 'timeout_seconds', self.timeout_seconds) # ... 后续超时逻辑保持不变
内容的提问来源于stack exchange,提问作者Yassine Belmamoun
相关产品推荐
相关产品推荐

