Django 异步视图中的多个并发请求
Posted
技术标签:
【中文标题】Django 异步视图中的多个并发请求【英文标题】:Multiple concurrent requests in Django async views 【发布时间】:2022-01-23 03:58:20 【问题描述】:从 3.1 版起,Django 支持异步视图。我有一个在 uvicorn 上运行的 Django 应用程序。我正在尝试编写一个异步视图,它可以同时处理对自身的多个请求,但没有成功。
我见过的常见示例包括从视图内部进行多个慢速 I/O 操作:
async def slow_io(n, result):
await asyncio.sleep(n)
return result
async def my_view(request, *args, **kwargs):
task_1 = asyncio.create_task(slow_io(3, 'Hello'))
task_2 = asyncio.create_task(slow_io(5, 'World'))
result = await task_1 + await task_2
return HttpResponse(result)
这将在 5 秒而不是 8 秒后为我们生成“HelloWorld”,因为请求是同时运行的。
我想要的是同时处理对 my_view 的多个请求。例如。我希望这段代码能在 5 秒内同时处理 2 个请求,但需要 10 秒。
async def slow_io(n, result):
await asyncio.sleep(n)
return result
async def my_view(request, *args, **kwargs):
result = await slow_io(5, 'result')
return HttpResponse(result)
我用这个命令运行 uvicorn:
uvicorn --host 0.0.0.0 --port 8000 main.asgi:application --reload
Django doc 说:
主要好处是能够在不使用 Python 线程的情况下为数百个连接提供服务。
所以有可能。
我错过了什么?
更新: 看来,我的测试设置是错误的。我在浏览器中打开多个标签并一次刷新它们。详情请见我的answer。
【问题讨论】:
如何发送请求? @lucutzu33 我在浏览器中打开多个选项卡并一次全部刷新。这并不精确,但大超时(5、10 秒)足以看出选项卡加载之间的延迟比刷新它们所需的时间长得多。我还添加了时间戳记录,它显示请求之间的准确时间为 5 秒。 【参考方案1】:您的问题是您编写的代码类似于同步版本。你等待每个函数的结果,只有在这之后,你才等待下一个函数。
你只需要使用像gather
这样的异步函数来异步运行所有任务:
import asyncio
async def slow_io(n, result):
await asyncio.sleep(n)
return result
async def my_view(request, *args, **kwargs):
all_tasks_result = await asyncio.gather(slow_io(3, 'Hello '), slow_io(5, "World"))
result = "".join(all_tasks_result)
return HttpResponse(result)
【讨论】:
请检查我的answer。您描述了问题中的第一个案例,它按预期工作。但我说的是第二种情况 - 视图中的单个异步任务,我必须等待,才能将结果放入响应中。以及对该视图的多个同时请求,我希望同时处理这些请求。它似乎与同步或异步堆栈无关,而是我如何测试它以及浏览器和 Django 的工作方式。【参考方案2】:这里是 Django 3.2 上的 sample project,带有多个异步视图和测试。我以多种方式对其进行了测试:
来自 Django 测试客户端的请求按预期同时处理。 按预期同时处理来自单个客户端的对不同视图的请求。 按预期同时处理来自不同客户端的对同一视图的请求。什么没有按预期工作?
单个客户端对同一视图的请求一次由一个客户端处理,我没想到会这样。Django中有一个警告doc:
只有在您的站点中没有加载同步中间件时,您才能获得完全异步请求堆栈的好处。如果有一个同步中间件,那么 Django 必须为每个线程使用一个线程请求为其安全地模拟同步环境。
可以构建中间件来支持同步和异步上下文。 Django 的一些中间件是这样构建的,但不是全部。 要查看 Django 必须适应哪些中间件,您可以打开 django.request 记录器的调试日志记录并查找有关“同步中间件……已适应”的日志消息.
因此,即使在裸 Django 中,也可能是一些同步中间件导致问题。但它也指出 Django 在这种情况下应该使用线程,而我没有使用任何同步中间件,只使用标准的。
我的最佳猜测是,它与客户端-服务器连接有关,与同步或异步堆栈无关。尽管 Google 表示出于安全原因,浏览器会为每个选项卡创建新连接,但我认为:
如果出于经济原因,URL 相同,浏览器可能会为多个选项卡保持相同的连接。 如它所述,Django 会根据连接而不是根据请求创建异步任务或线程。需要检查一下。
【讨论】:
以上是关于Django 异步视图中的多个并发请求的主要内容,如果未能解决你的问题,请参考以下文章