Py学习  »  Django

Django 3.0 ASGI 指南及其性能

Python程序员 • 4 年前 • 712 次点击  


2019 年 12 月,Django3.0 发布,它具有一个有趣的新特性——支持 ASGI 服务器。我对这特性非常感兴趣。当我检查 Python Web 异步框架的性能时,我发现它的性能比 Python Web 同步框架的性能要快3 到 5 倍。


因此,我开始利用非常简单的 Docker 设置来测试 Django3.0 的性能。虽然结果并不非常显著,但还是给人留下了深刻印象。在此之前,你需要对 ASGI 有所了解。


在 ASGI 之前,是 WSGI


2003 年,诸如Zope、Quixote之类的各式各样的 Python Web 框架都附带了自己的 Web 服务器,或者拥有自己的本地接口来与流行的网络服务器(如 Apache)进行通信。


成为一名 Python Web 开发人员意味着要全身心投入到一个完整的技术栈中,但是如果需要另一个框架时,则必须重新学习所有内容。可以想象到这会导致碎片化。PEP 333,即 Python Web 服务器网关接口 v1.0,尝试通过定义一个被称为 WSGI(Web Server Gateway Interface)的简单标准接口来解决这个问题。它的卓越之处在于它的简单性。


实际上,整个 WSGI 规范可以被简化(有意地忽略一些繁琐的细节)为,一个服务器端(server side)调用由框架或者应用提供的可调用对象(如,Python 函数或者具有调用方法的类)。如果存在一个既可以作为调用方,也可以作为被调用方的组件,那么就可以称之为整个处理流程的一个“中间件(middleware)”或者中间层。因此,WSGI 组件可以通过链式组合来处理请求。

当连接越来越简单时,欢愉随之而来


WSGI 如此流行,以至它不仅被诸如 Django 和 Pylons 之类的大型 Web 框架所采用,还被诸如 Bottle 之类的微框架所采用。您最喜欢的框架可以嵌入到任何与 WSGI 兼容的应用服务器中,并且可以正常工作。它是如此简单和直观,以至于没有理由不使用它。


扩大规模的“障碍”


如果我们对 WSGI 非常满意的话,为什么还要提出 ASGI 呢?如果仔细检查网络请求的整个处理流程,那么答案就非常明显了。您可以查看 在Django中网络请求如何工作 的动画。请注意,在数据库查询之后且响应发送之前,框架是如何等待的。这就是同步处理的缺点。


坦白说,直到 2009 年 Node.js 出现时,这种缺陷才变得明显或紧迫。Node.js 的创建者 Ryan Dahl 受到 C10K 问题的困扰,即为什么像 Apache 这样的流行 Web 服务器无法处理 10,000 个或更多的并发连接(给定典型的 Web 服务器硬件,内存将会耗尽)。他思考到 “查询数据库时,软件在做什么?”。

看起来她像是一直在等待中


当然,答案是什么也没有发生。它正在等待数据库的响应。Ryan 认为网络服务器根本不应该去等待 I / O 活动。换言之,它应该切换为处理其他请求,并在较慢的活动完成时能得到通知。使用此技术,Node.js 可以在更少的内存和单个线程上为更多数量的用户提供服务!


越来越明显的是,基于异步事件的架构是解决多种并发问题的正确方法。也许这就是为什么 Python 的创建者 Guido 亲自为 Tulip 项目提供语言级别支持的原因,这个项目后来成为了 asyncio 模块。最终,Python 3.7 添加了新的关键字 async 和 await ,用于支持异步事件循环。这不仅对如何编写 Python 代码而且对代码执行也具有重大意义。


Python 的两个世界


虽然使用 Python 编写异步代码非常简单,在函数定义前添加 async 关键字,但是您必须非常小心,不要打破一个重要的规则:不要随意混合使用同步代码和异步代码。


这是因为同步代码可以阻止异步代码中的事件循环。这种情况会使您的应用程序陷入停顿。正如 Andrew Goodwin 所写:这会将您的代码分为两个世界——具有不同库和调用风格的“同步代码”和“异步代码”。


当两个世界碰撞时,结果可能会出乎意料


回到 WSGI,这意味着我们无法编写异步可调用对象并将其嵌入。WSGI 是为同步世界编写的。我们将需要一种新的机制来调用异步代码。但是,如果每个人都编写自己的一套机制,我们将回到最初的不兼容地狱。因此,对于异步代码,我们需要一个类似于 WSGI 的新标准。因此,ASGI 诞生了。


ASGI 还有其他一些目的。但是在此之前,让我们看一下两种类似的 Web 应用程序“Hello World”,它们分别采用 WSGI 和 ASGI 风格。


使用 WSGI :



使用 ASGI :



注意:传递给可调用对象的参数变化。该参数scope类似于先前的参数environ。该参数send对应于start_response。但是参数receive是新的。它允许客户端使用允许双向通信的协议(如 WebSockets)若无其事地将消息发送到服务器。


与 WSGI 一样,ASGI 可调用对象可以一个接一个地链接在一起,以处理 Web 请求(以及其他协议请求),即链式调用。实际上,ASGI 是 WSGI 的超集且可以调用 WSGI 可调用对象。ASGI 还支持长时间轮询,慢速流媒体和其他响应类型,而无需侧向加载,从而可以加快响应速度。


因此,ASGI 引入了构建异步 Web 界面和处理双向协议的新方式。客户端或服务器端都无需等待对方进行通信——这可以随时异步发生。现存且以同步代码编写的基于 WSGI 的 Web 框架将不支持这种事件驱动的工作方式。


Django 演变


同时,想将所有这些异步优势带给 Django 面临一个重大的问题 —— 所有 Django 代码都是以同步风格编写的。如果我们需要编写任何异步代码,那么需要一个采用异步风格编写的整个 Django 框架的副本。换句话说,创建两个 Django 世界。


好吧,不要惊慌——我们可能不必编写一个完整的副本,因为有聪明的方式可以在两个世界之间重用一些代码。正如领导 Django Async 项目的安德鲁·戈德温(Andrew Godwin)正确地指出的那样,“这是 Django 历史上最大规模的修订之一”。一个雄心勃勃的项目采用异步风格重新实现 ORM、请求处理程序(request handler)、模板渲染器(Template renderer)等组件。这将分阶段进行,并在多个版本中完成。如下是安德鲁的预想(这不视为已提交的时间表):


  •  Django 3.0-ASGI 服务器

  •  Django 3.1-异步视图(请参见下面的示例)

  •  Django 3.2 / 4.0-异步 ORM


您可能正在考虑其余的组件,例如模板渲染、表单和缓存等。它们可能仍然保持同步或者其异步实现会纳入到将来的技术路线中。但是以上是 Django 在异步世界中发展的关键里程碑。


这使我们进入了第一阶段。


Django 谈 ASGI


在 3.0 中,Django 可以在“外部异步、内部同步”模式下工作。这样就可以与所有已知的 ASGI 服务器进行通信,例如:


  •  Daphne —— 采用 Twisted 编写的 ASGI 引用服务器(an ASGI reference server, written in Twisted)

  •  Uvicorn —— 基于 uvloop 和 httptools 的快速 ASGI 服务器(fast ASGI server based on uvloop and httptools)

  •  Hypercorn —— 基于 sans-io hyper、h11、h2 和 wsproto 库的 ASGI 服务器(an ASGI server based on the sans-io hyper, h11, h2, and wsproto libraries)


需要重申一下,Django 内部仍采用线程池(threadpool)同步处理请求。但是,底层的 ASGI 服务器将异步处理请求。


这意味着现有的 Django 项目无需更改。可以将此更改视为 HTTP 请求能够进入 Django 应用程序的新接口。


这是 Django “由外向内”转型的重要第一步。您也可以在通常更快的 ASGI 服务器上开始使用 Django。


如何使用 ASGI?


每个 Django 项目(从版本 v1.4 开始)都附带一个 wsgi.py 文件,该文件是 WSGI 处理程序模块。部署到生产环境时,您会将 WSGI 服务器(如 gunicorn)指向该文件。例如,您可能已经在 编写 Docker 文件中看到了这一行:



如果您创建一个新的 Django 项目(例如,通过运行 django-admin startproject 命令创建的),那么您将在 wsgi.py 同级目录下找到一个全新的文件 asgi.py。您将需要将 ASGI 服务器(如 daphene)指向此 ASGI 处理程序文件。例如,上面的行将更改为:



请注意:这需要有 asgi.py 文件。


在 ASGI 下运行现有的 Django 项目


Django 3.0 之前创建的所有项目都没有 asgi.py。那么如何创建一个呢?这相当简单。


这是 Django 项目的 wsgi.py 和 asgi.py 的并排比较(省略了文档字符串和注释):



如果您很难找出区别,请让我为您提供帮助——将“wsgi”替换为“asgi”。是的,非常简单,获取现有的 wsgi.py 文件并执行字符串替换`s/wsgi/asgi/g`。


陷阱


应注意不要在 asgi.py 的 ASGI 处理程序中调用任何同步代码。例如,如果由于某种原因在 ASGI 处理程序中调用某个 Web API,那么它必须是一个可异步调用的。


性能:ASGI vs WSGI


我做了一个非常简单的性能测试,尝试了 使用 ASGI 和 WSGI 配置的 Django 轮询项目。像所有性能测试一样,您应该在大量的测试中可以得出像我这样的结果。我的 Docker 设置包括 Nginx 和 Postgresql。实际的负载测试是使用多功能的 Locust 工具完成的。


该测试用例是在 Django 轮询应用程序中打开一个轮询表单,然后提交随机决议值。当有 n 个用户时,它每秒发出 n 个请求。等待时间在 1 到 2 秒之间。


快是不够的,还需要避免失败率


下面显示的结果表明:与 WSGI 模式相比,在 ASGI 模式下运行时,同时刻的用户数量增加了大约 50%。



随着并发请求数量的增加,WSGI 或 ASGI 处理程序将无法处理超出某个特定点的状态,从而导致错误或失败。WSGI 失败出现后,每秒请求数变化很大。即使出现失败后,ASGI 的性能也要稳定得多。


如表中所示,在我的机器上,对于 WSGI 的同时刻用户数量约为 300,而对于 ASGI 的同时刻用户数量约为 500。服务器可以正确处理而不发生错误,用户数量增加了约 66%。您的结果记录表可能会略有不同。


常见问题


最近我在 BangPypers 上讨论 ASGI 和 Django,读者提出了很多有趣的问题(即使在活动结束后)。所以我想在这里回答读者提出的问题(不分先后):


问:Django Async 与 Channels 是否相同?


创建 Channels 用以支持像 Websockets 和长轮询 HTTP 等这样的异步协议。Django 应用程序仍然可以同步运行。Channels 是官方的 Django 项目,但不是核心 Django 的一部分。


Django Async 项目除了支持使用同步代码来编写 Django 项目,还支持使用异步代码来编写 Django 应用程序。Async 是 Django 核心的一部分。


两个项目均由安德鲁·古德温(Andrew Goodwin)领导开发。


在大多数情况下,这些都是独立的项目。可以使用这两者之一或者同时使用这两者来开发您的项目。例如,如果您需要通过 Web sockets 支持聊天应用程序,则可以使用 Channels 而不使用 Django 的 ASGI 接口。另一方面,如果要在 Django 视图中创建异步函数,那么将必须等到 Django Async 对视图支持。


问:Django 3.0 中有任何新的依赖项吗?


仅安装 Django 3.0 会将以下内容安装到您的环境中:



该 asgiref 库是一个新的依赖。它包含了同步到异步和异步到同步函数封装,因此可以从异步调用同步代码,反之亦然。它还包含一个 StatelessServer 和一个 WSGI-to-ASGI 适配器。


问:升级到 Django 3.0 是否会破坏我的项目?


版本 3.0 可能与之前的版本 Django 2.2 相比有很大的变化。但这有点误导人。Django 项目未完全遵循语义版本(主版本号更改可能会破坏某些 API),并且差异点会在“发布过程”页面中进行说明。


在 Django 3.0 的发布说明中,将会注意到几乎是没有严重的向后不兼容更改。如果您的项目不使用它们中的任何一个,那么您可以进行升级而无需任何修改。


那么为什么版本号从 2.2 跳到 3.0?在发布节奏部分对此进行了解释:


* 从 Django 2.0 开始,版本号将使用一种宽松的语义版本控制形式,这样 LTS 之后的每个版本都会碰到下一个“零号”版本。例如:2.0、2.1、2.2(LTS)、3.0、3.1、3.2(LTS)等。


由于最新发布 Django 2.2 是长期支持(LTS)版本,因此接下来的发布必须将主版本号增加到 3.0。差不多就是这样的情况!


问:我可以继续使用 WSGI 吗?


可以。异步编程可以看作是在 Django 中编写代码的一种完全可选的方式。使用 Django 时采用熟悉的同步方式将继续有效并受到支持。



安德鲁写道:


  • 即使利用处理程序有了完全异步的路径,也必须维护 WSGI 兼容性;为了做到这一点,WSGI 程序将与新的 ASGI 程序并存,并在一次性事件循环中运行系统——使其在外部保持同步,而在内部保持异步。

  • 如果选择以这种方式运行,这将允许异步视图执行多个异步请求并且甚至在 WSGI 内部启动短暂的协程。但是,如果选择在 ASGI 下运行,那么您还将获得相互之间不阻塞并且使用更少线程的请求的优势。


问:什么时候可以在 Django 中编写异步代码?


如之前在 Async Project 路标中所述,预计 Django 3.1 将引入异步视图,该视图将支持编写异步代码,例如:



您可以随意混合使用异步和同步视图,中间件和测试。Django 将确保您始终拥有正确的执行上下文。听起来很棒,对吗?


在撰写本文时,此补丁几乎可以肯定会在 3.1 中发布,等待最终审查。


总结


我们介绍了许多关于 Django 支持异步功能的背景知识。我们试图理解 ASGI 及其与 WSGI 的差异。我们还发现,针对并发请求数量的增加,在 ASGI 模式下性能有所提高。还解决了许多与 Django ASGI 功能支持有关的常见问题。


我相信 Django 中的异步支持可以打破格局。这将是第一个发展为处理异步请求的大型 Python Web 框架之一。令人钦佩的是,这样做非常小心,并没有破坏向后兼容性。


我通常不会公开我的技术预测(坦率地说,其中许多事实已被证明多年)。我的技术预测就是这样——大多数 Django 部署将在五年内使用异步功能。


我很期待看看我的预测是否准确!


英文原文:https://arunrocks.com/a-guide-to-asgi-in-django-30-and-its-performance/ 
译者:小川姓丘

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/59994
 
712 次点击