Web | 是时候试试Django 3.1新的异步视图功能了

2020-12-01 11:32:04 浏览数 (1)

编写异步视图(async views)使你能够毫不费力地加速你的应用程序。随着Django 3.1最终支持异步视图,异步中间件和测试,现在是学习使用它的好时机。这篇文章探讨了如何开始使用Django 3.1提供的新异步视图。

目标

在这篇文章的结尾,你应该能够:

  1. 在Django中编写异步视图
  2. 在Django视图中发出非阻塞HTTP请求
  3. 使用Django的异步视图简化基本的后台任务
  4. 使用sync_to_async在异步视图中进行同步调用
  5. 说明何时应该使用或不应该使用异步视图

你还应该能够回答以下问题:

  1. 如果你在异步视图中调用同步任务怎么办?
  2. 如果在异步视图中进行同步任务和异步任务调用怎么办?
  3. 既然Django已经支持异步视图了,那么Celery还有用吗?

先决条件

如果你Django已经比较熟悉,那么在基于函数的视图中添加异步功能将变得非常直接简单。

本项目环境依赖

  1. Python >= 3.8
  2. Django >= 3.1
  3. Uvicorn
  4. HTTPX

什么是ASGI?

ASGI代表异步服务器网关接口。这是继WSGI以后一个现代的支持异步的服务器网关接口,为创建基于Python的异步Web应用程序提供了标准。

值得一提的另一件事是,ASGI与WSGI向后兼容的,即使你不准备转向编写异步应用程序,也可以将其从Gunicorn或uWSGI之类的WSGI服务器切换至Uvicorn或Daphne之类的ASGI服务器。

创建项目与应用

创建一个新的项目目录以及一个新的Django项目:

代码语言:javascript复制
$ mkdir django-async-views && cd django-async-views
$ python3.8 -m venv env
$ source env/bin/activate

(env)$ pip install django
(env)$ django-admin.py startproject hello_async .

你可以随意将virtualenv和Pip换成Poetry或Pipenv。

如果您使用Django内置开发测试服务器,你的项目可以启动,但实际上它不会真正异步运行它们,因此我们将使用Uvicorn来启动你的项目。

安装它:

代码语言:javascript复制
(env)$ pip install uvicorn

要使用Uvicorn运行项目,请从项目的根目录使用以下命令:

代码语言:javascript复制
uvicorn {name of your project}.asgi:application

在本例中,这将是:

代码语言:javascript复制
(env)$ uvicorn hello_async.asgi:application

接下来,让我们创建第一个异步视图。添加一个新文件以将视图保存在“hello_async”文件夹中,然后添加以下视图:

代码语言:javascript复制
from django.http import HttpResponse


async def index(request):
    return HttpResponse("Hello, async Django!")

在Django中创建异步视图函数就像创建同步视图函数一样简单-您只需要在前面添加async关键字即可。

更新urls.py:

代码语言:javascript复制
from django.contrib import admin
from django.urls import path

from hello_async.views import index


urlpatterns = [
    path("admin/", admin.site.urls),
    path("", index),
]

现在,在根文件夹的终端中运行如下命令:

代码语言:javascript复制
(env)$ uvicorn hello_async.asgi:application --reload

--reload标志告诉uvicorn监视文件中的更改,如果发现更改,则重新加载。

现在打开你的浏览器,访问http://localhost:8000/,你将看到:

代码语言:javascript复制
Hello, async Django!

这不是世界上最令人兴奋的事情,但是,嘿,这是一个开始。值得注意的是,使用Django的内置开发服务器运行此视图将获得完全相同的功能和输出。这是因为我们实际上没有在处理程序中执行任何异步操作。

异步视图中执行异步任务会发生什么?

值得注意的是,异步支持是完全向后兼容的,因此您可以混合使用异步和同步视图,中间件和测试。Django将在适当的执行上下文中执行每个操作。

为了说明这一点,请添加一些新视图,如下所示。我们分别创建了一个异步和同步的任务,然后在异步视图和同步视图中调用它们。

代码语言:javascript复制
import asyncio
from time import sleep

import httpx
from django.http import HttpResponse


# 异步任务
async def http_call_async():
    for num in range(1, 6):
        await asyncio.sleep(1)
        print(num)
    async with httpx.AsyncClient() as client:
        r = await client.get("https://httpbin.org/")
        print(r)

# 同步任务
def http_call_sync():
    for num in range(1, 6):
        sleep(1)
        print(num)
    r = httpx.get("https://httpbin.org/")
    print(r)


# 异步视图 - 无异步或同步任务
async def index(request):
    return HttpResponse("Hello, async Django!")

# 异步视图 - 调用异步任务
async def async_view(request):
    loop = asyncio.get_event_loop()
    loop.create_task(http_call_async())
    return HttpResponse("Non-blocking HTTP request")

# 同步视图 - 调用普通同步任务
def sync_view(request):
    http_call_sync()
    return HttpResponse("Blocking HTTP request")

更新urls.py:

代码语言:javascript复制
# hello_async/urls.py

from django.contrib import admin
from django.urls import path

from hello_async.views import index, async_view, sync_view

urlpatterns = [
    path("admin/", admin.site.urls),
    path("async/", async_view),
    path("sync/", sync_view),
    path("", index),
]

由于我们的异步任务里使用了httpx库,所以需要安装HTTPX:

代码语言:javascript复制
(env)$ pip install httpx

在服务器运行的情况下,浏览器访问http://localhost:8000/async/, 您应该立即看到如下响应。

代码语言:javascript复制
Non-blocking HTTP request

在您的终端中,您应该看到:

代码语言:javascript复制
INFO:     127.0.0.1:60374 - "GET /async/ HTTP/1.1" 200 OK
1
2
3
4
5
<Response [200 OK]>

在这个异步视图调用异步任务的案例里,HTTP响应在第一个异步任务执行之前已经返回。HTTP响应返回后,异步任务仍在执行直到结束。

接下来,浏览同步视图函数对应地址http://localhost:8000/sync/, 得到HTTP响应大约需要五秒钟:

代码语言:javascript复制
Blocking HTTP request

转到终端,可以看到:

代码语言:javascript复制
2
3
4
5
<Response [200 OK]>
INFO:     127.0.0.1:60375 - "GET /sync/ HTTP/1.1" 200 OK

在此,HTTP响应是在同步任务完成后才返回的。

小编注:以上两个对比可以看出在Django中异步视图中调用和执行异步任务是非阻塞的,执行效率非常高。那么如果在异步视图中调用同步任务呢? 答案是与同步视图执行同步任务无区别。当你希望使用Django异步视图提升你的代码效率时,不仅视图需要是异步的,其调用的任务函数也必须是异步的。

同步转异步(sync to async)

如果您需要在异步视图内调用同步任务(比如通过Django ORM与数据库进行交互),请使用sync_to_async作为包装器或装饰器。

例子:

代码语言:javascript复制
# hello_async/views.py

async def async_with_sync_view(request):
    loop = asyncio.get_event_loop()
    async_function = sync_to_async(http_call_sync)
    loop.create_task(async_function())
    return HttpResponse("Non-blocking HTTP request (via sync_to_async)")

使用前需要先从asgiref库导入这个方法:

代码语言:javascript复制
from asgiref.sync import sync_to_async

使用sync_to_async,原本阻塞HTTP响应的同步任务将会放在后台线程中处理,从而允许先返回HTTP响应再执行耗时的同步任务。

Celery与异步视图

很多人会问,Django已经有异步视图了,那么还需要Celery吗? 答案是看情况。

Django的异步视图提供了与任务或消息队列类似的功能,而且更简单。如果您正在使用(或正在考虑)Django,并且想做一些简单的事情(例如向新订阅用户发送电子邮件或调用外部API), 那么异步视图是一种快速轻松实现此目标的好方法。如果您需要执行大量,长时间运行的后台进程,则仍然需要使用Celery或RQ。

应该注意的是,为了有效地使用异步视图,您应该仅在视图中进行调用异步任务。另一方面,任务队列在单独的进程上使用工作程序,因此能够在多个服务器的后台运行同步调用。

顺便说一句,您绝对不必在异步视图和消息队列之间进行选择-您可以轻松地串联使用它们。例如:您可以使用异步视图发送电子邮件或对数据库进行一次性修改,但是Celery每晚在计划的时间清理数据库或生成并发送客户报告。

何时使用异步视图

对于未开发项目,请利用异步视图并尽可能以异步方式编写I / O流程。也就是说,如果大多数视图仅需要调用数据库并在返回数据之前进行一些基本处理,那么与传统同步视图相比,您不会看到多少效率上的提升。

对于已完成的项目,如果您几乎没有I/O进程,请坚持同步视图。如果确实有许多I/O进程,你需要衡量一下以异步方式重写它们的难度。将同步I/O重写为异步并不容易,因此您可能要在尝试重写为异步之前优化同步的I/O和视图。另外,将同步任务与异步视图混合在一起绝不是一个好主意。

在生产环境中,请务必使用Gunicorn来管理Uvicorn,以便获得高并发(通过Uvicorn)和并行性(通过Gunicorn工人)的优势。

代码语言:javascript复制
gunicorn -w 3 -k uvicorn.workers.UvicornWorker hello_async.asgi:application

结论

尽管这是一个简单的用例,但它应该使您大致了解Django的新异步视图打开的可能性。您可以在异步视图中尝试其他一些操作,例如发送电子邮件,调用第三方API以及写入文件。考虑一下代码中具有简单过程的视图,这些视图不一定需要直接向最终用户返回任何内容,可以将这些视图快速转换为异步视图。

作者: JACE MEDLIN,原文地址:https://testdriven.io/blog/django-async-views/

代码语言:javascript复制
Love&Share [ 完 ]对了,看完记得一键四连,这个对我真的很重要。

0 人点赞