当Andrew Godwin在PyCon18的演讲上介绍Taking Django Async时,Channels已经进入了2.0时代,准确的说,如果按照5月份的时间算起来,Channels应该算进入2.1版本时代了。Channels的2.0时代也是伴随着Django升级至2.0之后放弃Python2的重大改变而做出了改变,带给社区了更多可以选择的异步方案。
Channels 2.0的进化
在Channels发布的时候,当时在我看完整体的大概设计之后我觉得我完全使用不到它。原因来自于几个方面:一方面是Channels早期版本的异步功能主要是服务于Websocket,而我们项目中并没有使用;第二点来自于实现方式太过于复杂,虽然带来了Background jobs功能的支持,但是这部分功能社区更多使用的还是Celery的方案(也是我们在用的),因此带来的功能并非那么吸引人,不至于让我们迁移到新的Django框架上。另外,实现上还需要依赖实现复杂的Twisted和第三方服务如Redis做layer,整体的复杂度也是十分的高,一旦出现了问题,解决起来就十分的蛋疼。不过目的也是十分明确,在Python本身的限制下,使用同步方式编写(伪)异步代码。
实际上Channels 1.x版本带来的问题还是很多的,除此之外它还选择了与社区推荐的方式不兼容的道路,导致生态无法借助更多社区的力量,于是Channels2.0版本对Channels进行了大量的重写,官方的说法是约75%左右的代码进行了重写,将异步化代码与同步化代码的处理进行了分离,简化了服务层数,代码量和复杂度也下降了不少。除了保留了与1.x版本兼容的以同步方式写异步代码以外,新加入了基于asyncio的模式。
借助Channels的赋能,你可以在不迁移大部分代码的前提下,将必要的请求迁移至异步化处理模式,而这样仅仅是需要做一部分的代码调整。
from channels.generic.http import AsyncHttpConsumer
class BasicHttpConsumer(AsyncHttpConsumer):
async def handle(self, body):
await asyncio.sleep(10)
await self.send_response(200, b"Your response bytes", headers=[
("Content-Type", "text/plain"),
])
Channels 2.0仍然存在的问题
其实在一段时间内(甚至现在),社区是反对asyncawait关键字的,一方面是asyncawait就像癌症一样会扩散,这一点在Channels的使用中就可以实现一定的妥协。另外一点是来自于显示的切换会造成额外的心智负担,尤其是在于调试和故障诊断时。相对来说,更多开发者更喜欢gevent的方案,虽然使用了monkey-patch,但是切换是隐式的,不需要自己进行管理,最多只是在遇到问题时需要关心。
而面对C-Extionsion时,asyncio是非常无力的,毕竟生态问题永远是最大的痛。比如说最最基础的mysql(如果你使用PostgreSQL,可能你更幸运一点)的支持,社区推荐使用它的forkmysqlclient,但是这个库仍旧未做太多改动,仅仅是mysql-client支持Python3和修复了一下BUG。那么就带来了一些其他问题,比如阻塞问题。Channels的解决方案是依赖于依靠asgiref.sync.sync_to_async方法,本质则是使用了concurrent.futures.ThreadPoolExecutor。虽然这样是会比直接调用sync方法稍微快了一点,但是提升确实是有限。
未来
保证在未来的使用过程中,既可以进行sync开发又能进行async开发,Channels给我们一个非常不错的可期的未来。
在不远的将来,Django将会在2.2版本(计划于2019年1月份Final)中迎来async化的ORM和View支持,虽然默认并不开启,而且大多数使用线程池模拟得来的。实际上,真正异步化的支持将会在2020年发布的3.0版本中支持,届时,整个请求处理栈将会全部异步化,并且后续开始同步支持的API进行deprecation。
领取专属 10元无门槛券
私享最新 技术干货