我们如何在 django 中创建异步 API?

Posted

技术标签:

【中文标题】我们如何在 django 中创建异步 API?【英文标题】:How can we create asynchronous API in django? 【发布时间】:2020-09-12 08:14:26 【问题描述】:

我想创建一个异步的第三方聊天机器人 API,并在暂停 10 秒后回复“ok”。

import time

def wait():
    time.sleep(10)
    return "ok"

# views.py
def api(request):
    return wait()

我已经尝试过 celery,如下所示,我正在等待 celery 对视图本身的响应:

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)

但是这个解决方案是同步工作的,没有任何区别。我们怎样才能让它异步而不要求我们的用户继续请求直到收到结果?

【问题讨论】:

我认为您正在寻找的是定义一个周期性任务。请看django-celery-beat @gutsytechster 你能解释一下它是如何工作的吗? 其实你想在 10 秒内做什么 (sleep(10)) ? django celery beat 也不是答案。 celery 用于延迟或定期任务。它不会在异步非阻塞视图中转换阻塞 django 视图。如果您只是阻止一个相关的 django 工作线程或进程,则无法处理其他请求。除了新的 django 3.x 之外,Django 并没有阻塞。这可以是非阻塞的。一些意见。 (例如,不允许访问 ORM)在那里您可以创建一个异步视图。 您可能想看看频道。 channels.readthedocs.io/en/latest 【参考方案1】:

正如@Blusky 的回答中提到的: 异步 API 将存在于 django 3.X 中。 不早于

如果这不是一个选项,那么答案就是

请注意,即使使用 django 3.X 任何 django 代码,访问数据库的不会是异步的,它必须在线程(线程池)中执行

Celery 用于后台任务或延迟任务,但 celery 永远不会返回 HTTP 响应,因为它没有收到它应该响应的 HTTP 请求。 Celery 对异步也不友好。

您可能不得不考虑更改架构/实现。 看看你的整体问题,问问自己你是否真的需要 Django 的异步 API。

此 API 是用于浏览器应用程序还是用于机器对机器应用程序?

您的客户可以使用网络套接字并等待答案吗?

您能否在服务器端分离阻塞和非阻塞部分? 将 django 用于所有非阻塞、周期性/延迟(django + celelry)的所有内容,并使用 Web 服务器插件或 python ASGI 代码或 Web 套接字实现异步部分。

一些想法

使用 Django + nginx nchan(如果你的网络服务器是 nginx)

nchan 链接:https://nchan.io/ 您的 API 调用将创建一个任务 ID,启动一个 celery 任务,立即返回任务 ID 或轮询 url。

轮询 URL 将通过例如 nchan 长轮询通道进行处理。 您的客户端连接到与 nchan 长轮询通道相对应的 url,当您完成任务时,celery 将其解除阻塞(10 秒结束)

使用 Django + 一个 ASGI 服务器 + 一个手工编码视图,并使用类似于 nginx nchan 的策略

同上逻辑,不过你不用nginx nchan,而是自己实现

对所有阻塞 url 使用 ASGI 服务器 + 非阻塞框架(或只是一些手动编码的 ASGI 视图),其余部分使用 Django。

他们可能通过数据库、本地文件或本地 http 请求交换数据。

保持阻塞并在您的服务器上抛出足够的工作进程/线程

这可能是最糟糕的建议,但如果只是供个人使用, 并且你知道你将有多少并行请求,然后确保你有足够的 Django 工作人员,这样你就可以承受阻塞。 在这种情况下,您会为每个慢速请求阻止整个 Django 工作人员。

使用网络套接字。例如使用 Django 的频道模块

Websockets 可以使用 django 通道模块 (pip install channels) (https://github.com/django/channels) 的 django 早期版本 (>= 2.2) 实现

您需要一个 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 应用程序服务器。

【讨论】:

我如何才能直接接收到通道的 HTTP 请求,我猜我必须使用 Web 套接字,并且我想将它作为 API 提供。那么,渠道如何在这种情况下提供帮助? 这正是我的问题,因为我不知道您的场景或用例。如果是浏览器应用程序,您可以将 websockets 用于异步部分。如果你想拥有一个纯粹的 M2M api,那当然不是。我刚刚体验到,有时看到略有不同的建议可能会很有趣。 增强了我的回答。如果您可以使用 Django 3 并且愿意使用最前沿的功能,那么这就是您要走的路。我个人将稍旧的技术用于生产,并将非常新的技术用于玩具项目、个人项目和概念验证项目。如果我得到确凿的结果,我会考虑在生产中使用。 它不仅适用于浏览器,API 可以用于任何 androidios 应用程序或任何能够发送 http 请求的东西。所以websockets不会工作。我在想 os 之类的 asyncio 或任何其他异步库等待功能(主从概念),但不确定它是否是最好的方法。如果 celery 的工作原理类似于 asyncio 的 await,它还提供了一个超时功能? 我对 nginx 不太了解,你能判断 asyncio await 还是 nginx 会是更好的解决方案吗?【参考方案2】:

最好的选择是使用 futur async API,它将在 Django 3.1 版本中提出(已经在 alpha 中可用)

https://docs.djangoproject.com/en/dev/releases/3.1/#asynchronous-views-and-middleware-support

(但是,您需要使用 ASGI Web Worker 才能使其正常工作)

【讨论】:

我必须使用 await 功能来实现这一点,这不会阻塞 API 吗?芹菜中的超时也可能做同样的事情,不是吗? 只要您处于异步循环中,您就可以使用create_task (docs.python.org/3/library/…) 无需等待即可添加异步任务 当然,你可以使用 celery,但是,你需要添加一个 Celery worker,一个代理服务器,最后是一个 Celery Heartbeat,并将所有的 Celery 堆栈添加到你的项目中。跨度> 如果你使用 asyncio,你可以使用 celery,但可能只是使用线程池来处理慢速任务并使用你的阻塞代码应该没问题。 @gelonida 你能分享一个例子作为答案吗【参考方案3】:

Checkout Django 3 ASGI(异步服务器网关接口)支持:https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/

【讨论】:

这和@Bluesky 的回答不一样吗?

以上是关于我们如何在 django 中创建异步 API?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django 迁移中创建 GIN 索引

我们如何使用 API 从 IDE 在 Kafka 中创建主题

如何在java中创建独立的子段?

如何在 Django 中创建团队负责人/组负责人?

如何在 C# 中创建异步方法?

如何在 berkelydb java edition db base api 中创建日志文件