Py学习  »  Django

Django Channels 2.0 发布

Python程序员 • 6 年前 • 729 次点击  

Andrew Godwin

作者简介 

Django核心开发

程序员、演说家 

私人飞机飞行员 

住在美国三藩的英国人

毕业于

牛津大学莫德林学院


这个版本等了很久,不过还好,我终于可以宣布 Channels 2.0 正式发布了,配套发布的还有 Daphne 2.0 和 channels_redis 2.0(就是原来的asgi_redis)。Channels 2 是一个重构版本,完全改变了内部的运行原理和代码结构,希望能让项目变得更好。


Channels 2 实际上还没有完全发布 -- 它还缺少 Channels 1 中的一些关键特性(如 multiplexing),以及一个深度的教程和示例代码 -- 不过总体来说项目可以说是完成了的,已经可以为这个项目写些东西了。


当然,我很擅长做大的重构 -- 比如我把south重构为Django Migrations,这次也是差不多的程度,保留大多数核心概念,但是代码几乎全部重换掉了。我不鼓励这样规模的重构,即便做的时候我的心里也感到不安 -- 毕竟在Django的世界中,后向兼容是一个一直被保持的良好传统 -- 但是我认为这次重构是必要的,这里我就来解释一下这样做的原因。

Channel Layers


Channel 1 设计的一个基石就是运行了网络终止码 -- 这允许我们将 HTTP 请求和 Websocket 帧转换为事件 -- 而这些事件由独立进程去处理。


这个想法看起来有点奇怪,确实很奇怪 -- 这个做法相比于 WSGI 的简洁模型显得太复杂了,WSGI 就只是直接将应用导入进来然后在进程内运行就行了。当时那样做是必要的,因为当时我给自己设定了一个限制:支持 Python2.7。


这意味着我无法使用Python内置的 async 特性,也就是说我必须把 Channels 和其他的同步组件区分开来。这样我就需要为应用代码单独设置一套事件循环,并从一个队列里拉取事件 -- 就像大家知道的 runworker -- 也因此,我需要在一个单独的线程或进程中运行一个基于 twisted 的服务器,这样它才不会被同步代码所阻塞,以便可以同时处理大量的请求。


当然,在大型的基于事件的系统中,你需要一种方法在进程之间传递消息 -- 在 Channels 中就是 channel layer -- 所以我重用了这种机制,使得服务器和 Worker 之间可以通信,也可以用它来广播事件(你也可以用它做其他的任务,也就是说 Channel 2 中这个机制仍旧存在)。


这个模型运行的意外地好,考虑到 -- 并发和延迟都没有像其他人预期的那样糟(不过确实也没比第一版的原型好多少)-- 但是它使得部署过程增加了两个额外的步骤(一个 Redis/RabbitMQ 做队列,和一个单独的工作进程),并且扩展方案更复杂了。我应该部署更多的 Worker ?还是增加更多的 Redis 阵列?还是运行更多的 Daphne 实例?


简化


我18个月前就意识到我可能需要重构并且简化这个程序,但是当时我太害怕,或者说太自负,以至于无法在没有很强的理由的情况下去做出这种不兼容的改动。对亏了几个 Django 和 Python 社区的大神级的朋友给我了我建议,这个建议虽然我之前也知道,但是他们让我更能下定决心:不兼容 Python2.7 没有关系(这个决定在我2014年编写第一版时并不像现在这样显而易见),去做一个完全异步的版本。


下定决心后,我退回来重新思考和设计 Channels ,以便于它更好地适应目前的状况 -- 不是从内部代码的角度考虑,而是从最终用户的角度考虑。我为 API 设计以及部署文档打了一个草稿,并预先确定了开发者用户能够使用的概念模型,然后再确定实现的最佳方式是什么。


我心里最确定的一件事就是请求还是应该在进程内完成。如果我想让 ASGI 成为 WSGI 优秀的继任者 -- 我正在为此而努力 -- 那么 ASGI 就需要和 WSGI 一样简洁优雅,允许服务有一个类似的接口。因此,我称 Channels 1 为 “ASGI”,并将它一分为二:其中一部分负责将 HTTP 和 WebSocket 转化为一系列的事件(这会是新的 ASGI 的一部分),另外一部分构建 Channel Layers 和跨事件通信(成为 Channel Layer 标准)。


这样,我就把 ASGI 构建为了真正的、简洁的 WSGI 的继任者 -- 一个你可以传入内容的可调用对象,并且能够在另一个进程中运行应用(进一步,允许你在应用中嵌套应用,也就是支持中间件的编写)。下面是一个 WSGI 程序的基本接口:

下面是 ASGI 应用的基本接口:

WSGI 的 environ,告诉你一个链接的信息,在 ASGI 中对应的是 scope。这部分很好理解。不太好理解的部分是,ASGI 不仅支持简单的 HTTP 请求,它把 WSGI 中“调用这个函数,函数内部会调用 start_response方法,并传入必要的数据”变成了“运行它的协程,并传入发送和接收的可等待对象”。


新的 ASGI 标准没有任何针对于 Django 的内容,或者 Channel Layer 的内容,或者其他的什么 -- 它只做将网络协议转化为事件这一件事。它甚至自带了一个 WSGI 到 ASGI 的转换器,以便于你能够在 ASGI 服务器中运行一个 WSGI 的应用。这种后向兼容是一个标准应该有的,但是在 Channels 1 中却很难做到。


Channel Layers 仍然保留在 Channels 中,不过现在它只用于服务器进程间的事件通信 -- 类似“聊天室中有一个新消息”,或者“这个用户有一个通知”,然后 ASGI 应用获得这些消息并处理他们,而不负责把反馈的消息传递给 Websocket。关键是,它们不再需要处理所有的 HTTP 请求了,这样事情就简单多了。


我很期望 ASGI 在 Python 世界得到广泛使用,过去几年,我和几个 Python 框架以及服务器框架的维护者讨论过这个想法。现在 Channels 2 基本完工了,我要更加专注地推进这个事情了。如果社区中的人真的开始采用 ASGI,那么我就会推荐它成为PEP的一部分。


一致化组件


新的 ASGI 标准已经足够整洁,以至于它已经支持实现“一致化组件”了 -- 这个概念是 Simon Willison 在2009年的 DjangoCon EU 上提出来的。


这个概念的含义,简单点说,就是让 Channels 2 的每个部分都成为一个 ASGI 应用。每一个消费者是,路由类是,静态文件处理器是 -- 所有的部分都是。他们都是组件化地嵌套在一起。当你想在你的 ASGI 应用中做些事情的时候,你要做的就是把它挂在根路由下面。


这样做不仅让你的测试和调试更容易 -- 对于每一个部分你都能使用同样的工具 -- 它还允许你更容易地添加删除 Django 组件,这符合我一直追寻的一个原则:包含电池,但是必要时也易于移除。


甚至 Django 的 view 系统也被实现为了一个 ASGI 应用 -- 你可以传递 HTTP 请求给它,然后它会像往常一样调用一系列的中间件,最后执行 view 函数本身。但是,如果你愿意,你可以让它走不一样的路径。甚至在不同的 URL 下面配置不同的路径规则。


这样也更便于重用和复用,这很重要,这就是我喜欢的简洁。Channels 中的 URL 路由系统就是一个 ASGI 应用,它接收一系列其他的 URL 录音和其他的 ASGI 应用作为参数,然后再进行路由调用。更赞的是,它们全部都是异步的。也就是说,在 Channels 2 中,整个框架堆栈都是异步的,直到你可能编写了一个同步的 view,或者是同步的消费者。但是,我们还有......


异步消费者


在 Channels 2 中,消费者仍然是最基础的代码单元,就像 Django 中的 view 一样,但是它增加了一些新的技巧。在 Channels 1 中消费者必须是同步的(因为消费者端整体就是同步的)。而现在消费者都变成了 ASGI 应用,因为它们就是运行在异步服务器进程中的mini应用,这给全异步消费者的出现提供了基础。


这意味,如果你想打开一个 Websocket 链接10秒钟,打开就好了,不必担心它会阻塞整个线程:

当然,如果你觉得没必要用异步代码,你也可以不用 -- 毕竟编写异步代码更容易出错 -- 你还可以使用同步消费者。有趣的是,你可以在同步异步之间轻松切换,感谢 asgiref 提供的新的 AsyncToSync 和 SyncToAsync 帮助工具。这些工具能把任何异步可调用对象转换为同步的,反之亦然。这样,你就能够在一个异步消费者中使用 Django ORM 了(这是一个同步接口)。


让你能够编写原生异步代码,使得我们可以移除一些没有必要的特性了,比如延迟服务器,这个特性存在的原因就是因为之前没办法在不阻塞进程的情况下等待一段时间。如果你愿意,你还可以使用 AsyncToSync 来在一个同步消费者中运行一点 asyncio 的代码。


还有一些重大更新,一个支持 Django URL pattern 的路由系统(包含老式 URL 和新式路径类型),更好的 auth 集成,等等。你可以在 Channels 的文档中查看更详细的说明。


道歉


这种大的改动是有代价的。从用户角度来说,这个代价基本上是最大的一种:没有后向兼容。虽然很多概念保持了一致,但是如果你想从 Channels 1 升级上来,那么不可避免地,你需要调整你的代码了。


我做出这个决定真的很艰难。我曾试图找到一种办法来实现后向兼容,但是由于底层原理的不同,使得这种后向兼容真的难以实现,即便实现,兼容带来的维护成本也是我无法承受的。所以,最后,我们没能实现。Django 社区有很好的后向兼容的优良传统,但是很抱歉,我的 Channels 没能做到。


如果你问我为什么 Channels 一直没有被 Django 接纳为核心组件,这可能是原因之一。异步 Web 框架在 Python 中还是相对比较新的领域,这些框架通常都是不考虑 Python2 的。也因此,我四年前的一些假设,现在已经不成立了。


更糟糕的是,我花了很多时间克服我天生的固执,以屈服于这种改变。在公众场合说自己做错了,有时候真的很难,尤其是,像我这种,大多数时候都是自己一个人做项目的人。


整个过程都是我一个人完成的,没有任何 Python 社区或者 Django 社区的其他同伴们参与。当然,在我不知所措时,他们总是会给我很有用的建议。我很感谢 Django 核心团队的其他同伴们,给予了我足够的信任,相信我能够搞定这一切,并以 Django 之名推进项目向前发展。


另外一件我要做出改变的事情就是,从 Channels 2 开始,这个项目开始接受新的贡献者。我有一个不好的习惯,倾向于自己一个人做项目,苛求对项目完美设计的完整控制,这导致了没有足够的人手来进行 Channels 的开发。


Channels 2.0 还有一点点工作没有完成。剩下的这部分工作没有做,是因为完成比完美更重要,这部分没完成并不影响大体功能的发布。如果你有兴趣参与这个项目,请直接联系我,我会试着帮你在未完成的工作项中找到一些你可以帮忙的部分,并且必要的话,花点时间辅导你参与到项目中来。


接下来?


说了这么多,这个版本对于 Channels 来说意味着什么?


首先,重构后的结构便于增加一些很好的接口,如长轮询、支持更多的协议、增加一些基础电池,并且可以开始写一些介绍教程和示例项目了。我称这个版本为软发布。如果说在参与开源项目维护的这10年我学到的最重要的一件事是什么,那就是尽快发布版本,即便它还不完美。


其次,还有很多外围工作和跨项目合作要做。我希望总体改善 Python Web 生态系统,Django 是其中的一部分,跟其他项目合作还有很多事情要做。如果我们想保证 Python 在未来10年能够在后端开发中占据举足轻重的位置,我们需要做出很多努力。


最后,它还意味着 Channel 1 的支持就要变少了,可能只会做一些安全相关的修复,以及数据丢失类的 bug 的修复了。我还是不会终止支持,是因为我们的 Channel 2 没有实现后向兼容,而我们的 Channels 人手不足,除了我外,没有人手来做 Channel 1 最后这段时间的支持了。


Channels 会被合并入 Django 核心部分么?也许,但是这取决于 Django 内部和外部的很多因素。ASGI 会最终成为 WSGI 的继任者么?如果没人推进肯定不会,但是我会鼎力推进这个进展的。


我会休息一段时间,然后顺利从为大家带来困扰这件事情中振作起来么?当然会。我会记得 Channel 1 曾经帮助了很多人,很多网站运行在它之上,而且这一路以来我也学到了很多东西。感谢所有帮助过我,使用过 Channels 的人。我希望未来能够帮助更多的网站和项目,接受更加令人兴奋的挑战!


英文原文:https://www.aeracode.org/2018/02/02/channels-20/

译者:诗书塞外



今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/e1oV7mXmnE
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/6766
 
729 次点击