Django Channels实时推送

在一个HTTP访问周期里,如果要执行一个长时间任务,为了避免浏览器等待,后台必须使用异步动作。与此同时也要满足实时需求,用户提交了任务后可以随时去访问任务详情页面,在这里用户能够实时地看到任务的执行进度。

针对异步任务处理,我们使用了Celery把任务放到后台执行。Celery 是一个基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理,关于它的使用方法《网易乐得RDS设计》也有提到。Celery在处理一个任务的时候,会把这个任务的进度记录在数据库中。

实现任务的后台执行后,下一步就要解决实时地更新进度信息到网页的问题。从上一步可以知道,数据库中已经存在了任务的进度信息,网页直接访问数据库就可以拿到数据。但是数据库中的进度信息更新的速率不固定,如果使用间隔时间比较短的ajax轮询来访问数据库,会产生很多无用请求,造成资源浪费。综合考虑,我们决定使用WebSocket来实现推送任务进度信息的功能。网站是使用Django搭建的,原生的MTV(模型-模板-视图)设计模式只支持Http请求。幸好Django开发团队在过去的几年里也看到实时网络应用的蓬勃发展,发布了Channels,以插件的形式为Django带来了实时能力。下面两张图展示了原生Django与集成Channels的Django。

原生Django

集成Channels的Django

对比两张图可以看出,Channels为Django带来了一些新特性,最明显的就是添加了对WebSocket的支持。Channels最重要的部分也可以看做是任务队列,消息被生产者推到通道,然后传递给监听通道的消费者之一。它与传统的任务队列的主要的区别在于Channels通过网络工作,使生产者和消费者透明地运行在多台机器上,这个网络层就叫做channel layer。Channels推荐使用redis作为它的channel layer backend,但也可以使用其它类型的工具,例如rabbitmq、内存或者IPC。关于Channels的一些基本概念,推荐阅读官方文档。

背景到此为止,开始对我们的实现流程作介绍。

1.安装与配置

根据官方文档,channels可以直接集成到1.8版本之上的Django上。推荐直接使用pip安装。我们项目中使用了rabbitmq作为channels layer backend,因此还需要安装asgi_rabbitmq这个包。安装后修改django的settings.py配置文件如下:

配置项中routing指是Channels的路由配置,作用类似于Django中的urls.py,不同的是原生Django把与url相对应的函数叫做view,而Channels把相应函数叫做consumer。

2. 在routing.py添加channels路由

此处的写法类似urls.py中的内容,route的第一个参数指明了WebSocket的事件类型,第二个参数指明了对应的consumer,第三个参数按照WebSocket消息的路径名来作为filter,确保只有特定路径的WebSocket消息才会被路由。

另外我们可以看到这个新的路由文件并没有包含对http请求进行路由的信息,这是因为使用了Channels之后依然兼容旧的通过urls.py对HTTP请求进行路由的写法,因此不用对原项目的代码做修改,只需要添加新的WebSocket路由即可。因为在我们的需求里并不需要前台页面向后台发送数据,所以没有使用websocket.receive事件类型。

3. 编写consumer

对应routing中的信息,新建consumer.py文件添加以下内容:

@http_session是Channels提供的可以获取HTTP session的装饰器,这样就可以让WebSocket请求与原项目使用相同的一套认证方式,减少代码量。当服务器收到path为 /taskdetails/xx(xx为任务id)的WebSocket连接请求时,task_details_connect函数会被调用,如果服务器中有这个客户端的session,就把这个客户端加入到名字为 task-id 的Group里,并建立WebSocket连接,否则就拒绝连接。Group是Channels提供的一个类,使用它可以方便地对Group里的客户端发送广播消息。当服务器收到path为 /taskdetails/xx的WebSocket断开请求时,则把这个客户端剔除出特定的Group。

4. 编写Celery后台任务函数

使用@task装饰器装饰的方法就是Celery的任务,会在用户发送执行指令后异步执行。这个方法中模拟了5步操作,每一步完成后都会在数据库中更新记录,并把信息发送给特定的Group,广播到这个组里所有的WebSocket客户端。

5. 任务详情页JS代码

页面加载时首先根据后台传回的任务状态参数task_status进行判断,如果任务还在执行中就与后台建立WebSocket连接。建立连接后等待Celery后台任务推送进度信息,实时显示推送消息到网页上。

6. 启动daphne

区别与WSGI服务器,Channels使用ASGI(Asynchronous Server Gateway Interface)服务器来同时支持HTTP、HTTP2和WebSocket请求,这个服务器叫做daphne。在项目的wsgi.py同级目录下新建asgi.py文件,添加以下内容,此时运行manage.py runserver启用的就是daphne。

至此,一个简单的服务器实现推送流程就完成了。对于生产环境,如果对daphne的性能有疑问,还可以通过Nginx做HTTP与WebSocket流量作划分,把daphne作为专门的WebSocket服务器。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180125G043D900?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券