Django项目如何处理同时发起的多个请求并实现请求排队
先理清Django默认的并发处理逻辑
- 很多人本地跑
python manage.py runserver的时候会发现请求是挨个串行处理的,这是Django自带开发服务器的默认单线程逻辑,这东西纯为本地调试做的,生产环境绝对不能用。生产环境部署Django时,不管选Gunicorn、uWSGI这类WSGI服务器,还是Daphne、Uvicorn这类ASGI服务器,默认都会开多worker、多线程或者协程来并行处理请求,不会让所有请求堵在单条执行线上。 - 要是你的接口不涉及共享资源抢占——比如只是查个公开内容、改用户自己的不冲突的个人信息——根本不需要额外做排队,靠部署层的并发配置就能扛住普通流量,上来就给全站点加排队逻辑,只会把响应速度拖到没法用。
哪些场景才需要做请求排队
不要为了排队而排队,只有碰到以下场景的时候才需要针对性加排队/并发控制:
- 多个请求会同时修改同一条核心数据,比如同个商品的库存扣减、同个用户的余额变更,不加控制会出现超卖、余额扣错的问题
- 业务依赖的下游服务有严格并发限制,比如短信通道1秒最多允许发5条请求,超了就直接被限流报错
- 接口逻辑是高CPU/高内存消耗的计算任务,同时跑太多会直接把服务器资源打满
- 类似发帖、发送即时消息这类需要严格按用户提交顺序落库的场景
具体实现方案,按业务场景选
1. 数据库行锁实现单资源排队(优先选,性能损耗最小)
这个方案不需要引入任何额外组件,靠数据库本身的排他锁就能实现针对同一资源的请求排队,完全不影响其他无关请求的并发处理,90%的单数据竞争场景用这个就够。
比如库存扣减的场景,不要先查库存再算完更新,要在事务里加行锁,其他针对同一条记录的请求会自动阻塞等锁释放,自然形成排队:
from django.db import transaction from .models import Product def deduct_stock(product_id, count): with transaction.atomic(): # select_for_update会给查询到的行加排他锁,同记录的并发请求会在这里等待 product = Product.objects.select_for_update().get(id=product_id) if product.stock < count: return False, "库存不足" product.stock -= count product.save() return True, "扣减成功"
注意:这个锁只针对同一条数据行生效,比如用户A买商品1、用户B买商品2,两个请求完全不会互相阻塞,性能影响可以忽略。
2. 异步任务队列(适合不需要同步返回结果的耗时场景)
如果用户点完发送按钮不需要等所有逻辑执行完就能拿到响应——比如发批量通知、导出数据、发邮件——直接把任务丢到异步队列里,靠固定数量的worker控制并发,超出并发上限的任务自动排队等待执行。
最常用的组合是Celery搭配Redis/RabbitMQ做消息中间件:
- 先配置Celery的worker并发数,比如要求同一时刻最多跑3个发短信任务,就把对应队列的worker concurrency参数设为3
- 视图层收到请求后,不直接执行耗时逻辑,调用任务的
delay()方法把任务丢进队列,立刻给用户返回“提交成功,正在处理”的响应 - Celery worker会严格按入队顺序取任务执行,并发占满后新任务自动在队列里等待
代码示例:
# tasks.py 定义异步任务 from celery import shared_task from django.core.mail import send_mail from .models import User @shared_task def send_notice_email(user_id, content): user = User.objects.get(id=user_id) send_mail("系统通知", content, "noreply@example.com", [user.email]) # views.py 视图逻辑 def send_btn_view(request): if request.method == "POST": content = request.POST.get("content") # 不直接执行发信逻辑,丢进队列排队 send_notice_email.delay(request.user.id, content) return JsonResponse({"code":0, "msg":"发送请求已提交,稍后将送达"})
如果不想引入太重的Celery,Django生态里的Django Q是更轻量的原生任务队列,配置逻辑基本一致。
3. 分布式队列实现全局并发控制(适合有全局限流要求的场景)
如果需要严格控制某类接口的全局最大并发数,比如短信发送接口全局每秒最多处理5个请求,不管请求来自哪个用户、哪个实例,超了就排队,可以用分布式队列实现:
- 单实例部署的场景可以直接用Python标准库的
queue模块做内存队列,启动固定数量的工作线程从队列取任务执行,接口收到请求后把参数塞进队列,等待结果返回或者直接响应 - 多实例分布式部署的场景用Redis做队列载体,用Redis的List结构存待处理请求,搭配固定数量的消费者进程取任务执行,同时可以配合限流组件做一层拦截,超过承载能力的请求直接返回“当前人数过多,请稍后再试”
非常重要的提醒:绝对不要用无界队列,一定要给队列设置最大长度,超过长度直接返回友好提示,不然流量突增的时候队列会把服务器内存占满,直接导致整站宕机。
4. Nginx层做排队兜底
最外层可以用反向代理Nginx的limit_req模块做请求排队控制,可以按IP、按接口配置每秒允许处理的请求数,超出的请求会在Nginx层排队等待,队列长度、等待超时都可以自定义配置,避免突发流量直接把Django后端打垮。
几个容易踩的坑
- 不要试图用Django进程里的全局变量做排队锁,生产环境是多进程多线程部署的,全局变量在不同进程、不同线程间不共享,完全起不到并发控制的作用
- 不要给所有接口加排队逻辑,只给确实存在资源竞争、并发限制的接口加控制,不然整站QPS会降到和单线程处理一个水平,用户体验极差
- 所有排队逻辑都要设置超时时间,不能让用户无限制等待,超过超时时间直接返回提示,避免请求堆积
内容的提问来源于stack exchange,提问作者Sandeep

