我们如何在 Django 中创建异步 API?
How can we create asynchronous API in django?
我想创建一个第三方聊天机器人 API,它是异步的并在暂停 10 秒后回复 "ok"。
import time
def wait():
time.sleep(10)
return "ok"
# views.py
def api(request):
return wait()
我已经尝试过芹菜,如下所示,我在视图本身中等待芹菜响应:
import time
from celery import shared_task
@shared_task
def wait():
time.sleep(10)
return "ok"
# views.py
def api(request):
a = wait.delay()
work = AsyncResult(a.id)
while True:
if work.ready():
return work.get(timeout=1)
但是这个解决方案是同步工作的,没有区别。我们如何才能使其异步而不要求我们的用户继续请求直到收到结果?
最好的选择是使用 futur async
API,它将在 3.1 版本的 Django 上提出(已经在 alpha 中可用)
https://docs.djangoproject.com/en/dev/releases/3.1/#asynchronous-views-and-middleware-support
(但是,您需要使用 ASGI Web Worker 才能使其正常工作)
如@Blusky 的回答所述:
异步 API 将存在于 django 3.X 中。 之前.
如果这不是一个选项,那么答案就是 否。
另请注意,即使使用 django 3.X 任何访问数据库的 django 代码都将 不是 异步的,它必须在线程中执行 (线程池)
Celery 用于后台任务或延迟任务,但 Celery 永远不会 return HTTP 响应,因为它没有收到它应该响应的 HTTP 请求。芹菜也不适合异步。
您可能不得不考虑更改架构/实施。
查看您的整体问题并问问自己是否真的需要使用 Django 的异步 API。
这 API 是针对浏览器应用程序还是机器对机器应用程序?
您的客户可以使用网络套接字并等待答案吗?
能否在服务器端将阻塞部分和非阻塞部分分开?
将 django 用于非阻塞的所有事物,所有周期性/延迟的事物(django + celelry)并使用 web 服务器插件或 python ASGI 代码或 web 套接字实现异步部分。
一些想法
使用 Django + nginx nchan(如果你的网络服务器是 nginx)
Link 给 nchan:https://nchan.io/
您的 API 调用将创建任务 ID,启动 celery 任务,return 立即任务 ID 或轮询 url.
轮询 URL 将通过 nchan 长轮询通道进行处理。
您的客户端连接到对应于 nchan 长轮询通道的 url,并且只要您的任务完成(10 秒结束),celery 就会对其解除阻塞
使用 Django + 一个 ASGI 服务器 + 一个手写视图并使用类似于 nginx nchan 的策略
同上逻辑,但不用nginx nchan,自己实现
对所有阻塞 url 使用 ASGI 服务器 + 非阻塞框架(或只是一些手工编码的 ASGI 视图),其余使用 Django。
他们可能通过数据库、本地文件或通过本地 http 请求交换数据。
只要保持阻塞并在您的服务器上投入足够的工作进程/线程
这可能是最糟糕的建议,但如果仅供个人使用,
并且你知道你将有多少个并行请求,然后只需确保你有足够的 Django worker,这样你就可以承受阻塞。
在这种情况下,您将为每个缓慢的请求阻止整个 Django worker。
使用网络套接字。例如使用 Django 的频道模块
Websockets 可以使用早期版本的 django (>= 2.2) 和 django 通道模块 (pip install channels
) ( https://github.com/django/channels )
您需要一个 ASGI 服务器来为异步部分提供服务。您可以使用例如 Daphne ot uvicorn(频道文档对此进行了很好的解释)
附录 2020-06-01:调用同步 Django 代码的简单异步示例
以下代码使用 starlette 模块,因为它看起来非常简单和小巧
miniasyncio.py
import asyncio
import concurrent.futures
import os
import django
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Route
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pjt.settings')
django.setup()
from django_app.xxx import synchronous_func1
from django_app.xxx import synchronous_func2
executor = concurrent.futures.ThreadPoolExecutor(max_workers=2)
async def simple_slow(request):
""" simple function, that sleeps in an async matter """
await asyncio.sleep(5)
return Response('hello world')
async def call_slow_dj_funcs(request):
""" slow django code will be called in a thread pool """
loop = asyncio.get_running_loop()
future_func1 = executor.submit(synchronous_func1)
func1_result = future_func1.result()
future_func2 = executor.submit(synchronous_func2)
func2_result = future_func2.result()
response_txt = "OK"
return Response(response_txt, media_type="text/plain")
routes = [
Route("/simple", endpoint=simple_slow),
Route("/slow_dj_funcs", endpoint=call_slow_dj_funcs),
]
app = Starlette(debug=True, routes=routes)
例如,您可以 运行 此代码
pip install uvicorn
uvicorn --port 8002 miniasyncio:app
然后在您的网络服务器上将这些特定的 url 路由到 uvicorn 而不是您的 django 应用程序服务器。
检查 Django 3 ASGI(异步服务器网关接口)支持:
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
我想创建一个第三方聊天机器人 API,它是异步的并在暂停 10 秒后回复 "ok"。
import time
def wait():
time.sleep(10)
return "ok"
# views.py
def api(request):
return wait()
我已经尝试过芹菜,如下所示,我在视图本身中等待芹菜响应:
import time
from celery import shared_task
@shared_task
def wait():
time.sleep(10)
return "ok"
# views.py
def api(request):
a = wait.delay()
work = AsyncResult(a.id)
while True:
if work.ready():
return work.get(timeout=1)
但是这个解决方案是同步工作的,没有区别。我们如何才能使其异步而不要求我们的用户继续请求直到收到结果?
最好的选择是使用 futur async
API,它将在 3.1 版本的 Django 上提出(已经在 alpha 中可用)
https://docs.djangoproject.com/en/dev/releases/3.1/#asynchronous-views-and-middleware-support
(但是,您需要使用 ASGI Web Worker 才能使其正常工作)
如@Blusky 的回答所述: 异步 API 将存在于 django 3.X 中。 之前.
如果这不是一个选项,那么答案就是 否。
另请注意,即使使用 django 3.X 任何访问数据库的 django 代码都将 不是 异步的,它必须在线程中执行 (线程池)
Celery 用于后台任务或延迟任务,但 Celery 永远不会 return HTTP 响应,因为它没有收到它应该响应的 HTTP 请求。芹菜也不适合异步。
您可能不得不考虑更改架构/实施。 查看您的整体问题并问问自己是否真的需要使用 Django 的异步 API。
这 API 是针对浏览器应用程序还是机器对机器应用程序?
您的客户可以使用网络套接字并等待答案吗?
能否在服务器端将阻塞部分和非阻塞部分分开? 将 django 用于非阻塞的所有事物,所有周期性/延迟的事物(django + celelry)并使用 web 服务器插件或 python ASGI 代码或 web 套接字实现异步部分。
一些想法
使用 Django + nginx nchan(如果你的网络服务器是 nginx)
Link 给 nchan:https://nchan.io/ 您的 API 调用将创建任务 ID,启动 celery 任务,return 立即任务 ID 或轮询 url.
轮询 URL 将通过 nchan 长轮询通道进行处理。 您的客户端连接到对应于 nchan 长轮询通道的 url,并且只要您的任务完成(10 秒结束),celery 就会对其解除阻塞
使用 Django + 一个 ASGI 服务器 + 一个手写视图并使用类似于 nginx nchan 的策略
同上逻辑,但不用nginx nchan,自己实现
对所有阻塞 url 使用 ASGI 服务器 + 非阻塞框架(或只是一些手工编码的 ASGI 视图),其余使用 Django。
他们可能通过数据库、本地文件或通过本地 http 请求交换数据。
只要保持阻塞并在您的服务器上投入足够的工作进程/线程
这可能是最糟糕的建议,但如果仅供个人使用, 并且你知道你将有多少个并行请求,然后只需确保你有足够的 Django worker,这样你就可以承受阻塞。 在这种情况下,您将为每个缓慢的请求阻止整个 Django worker。
使用网络套接字。例如使用 Django 的频道模块
Websockets 可以使用早期版本的 django (>= 2.2) 和 django 通道模块 (pip install channels
) ( https://github.com/django/channels )
您需要一个 ASGI 服务器来为异步部分提供服务。您可以使用例如 Daphne ot uvicorn(频道文档对此进行了很好的解释)
附录 2020-06-01:调用同步 Django 代码的简单异步示例
以下代码使用 starlette 模块,因为它看起来非常简单和小巧
miniasyncio.py
import asyncio
import concurrent.futures
import os
import django
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Route
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pjt.settings')
django.setup()
from django_app.xxx import synchronous_func1
from django_app.xxx import synchronous_func2
executor = concurrent.futures.ThreadPoolExecutor(max_workers=2)
async def simple_slow(request):
""" simple function, that sleeps in an async matter """
await asyncio.sleep(5)
return Response('hello world')
async def call_slow_dj_funcs(request):
""" slow django code will be called in a thread pool """
loop = asyncio.get_running_loop()
future_func1 = executor.submit(synchronous_func1)
func1_result = future_func1.result()
future_func2 = executor.submit(synchronous_func2)
func2_result = future_func2.result()
response_txt = "OK"
return Response(response_txt, media_type="text/plain")
routes = [
Route("/simple", endpoint=simple_slow),
Route("/slow_dj_funcs", endpoint=call_slow_dj_funcs),
]
app = Starlette(debug=True, routes=routes)
例如,您可以 运行 此代码
pip install uvicorn
uvicorn --port 8002 miniasyncio:app
然后在您的网络服务器上将这些特定的 url 路由到 uvicorn 而不是您的 django 应用程序服务器。
检查 Django 3 ASGI(异步服务器网关接口)支持:
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/