Tornado学习序篇(I/O多路复用)

󰃭 2017-03-11

原创作品:首发u3v3, 转载请保留

地址:https://www.u3v3.com/ar/1283

作者ID:noomrevils

首发日期:2017.3.11

Python学习群:278529278 (欢迎交流)

前言

在上篇文章利用Python实现简单HTTP代理验证 我们使用Bottle框架实现了一个乞丐版的代理验证服务页面,但是Bottle并不适合放在生产环境下去应对大量的验证请求。我们需要一个性能更强劲的服务来支持高并发。在文章结尾有提到了tornadoaiohttp这两个异步Web框架。aiohttp这个框架还在发展阶段,而tornado已经被应用很多对性能有要求的场景中,并且有着良好的表现,因此后续将重点学习和掌握这个框架,tornadoDjango, Flask这些同步的web框架比起来,是另一种实现思路。其中最大的特色就是它的IO事件循环。为了能够更好的理解框架本身的机制,我们先来熟悉底层的原理,这里我不会介绍太多计算机术语,会有更多生活中的例子帮大家熟悉基本的概念和流程。

阻塞/非阻塞

我们先来看这样一对概念,阻塞/非阻塞

在下面一张图中,管道的两端分别是进程和数据,我们可以先把进程想象成工厂里的工人,数据是他要加工处理的配件,配件需要通过传送带到达他手边。

当这个工人一直在传送带前面等待着配件过来,无法抽身去做别的事情,这时这个工人就被阻塞在了这里。这种等待-就绪-执行的流程很符合人的认知习惯,但是也显的呆板,效率不足。

现在有三个传送带,需要这个工人处理,传送带上的数据并不一定同时到达,之前阻塞模型就不再适用。我们的工人怎么才能处理现在这种情况呢?聪明的工人想出了一个办法,他不在一条传送带前傻等,他开始在不同的传送带之间来回走动,检查那个传送带上有需要处理的配件。在传送带上配件间隔不断的出现的情况下,工人处理的很快,效率也很多,但是当传送带上没有配件,并且传送带的数量开始增多的时候,他觉得这么来回走,一直在做无用功。

上面的模型,工人已经是一种非阻塞的状态,通过忙轮询,他似乎可以处理多条处理线,但是效率是极端的不稳定,浪费了不少时间。

人都是喜欢偷懒的,但是光偷懒不讲效率是要被找谈话的,于是工人就在厂房里装一个监控。他自己不在传送带前来回转了,他坐在监视前打着盹,开开小差。 监视器连着摄像头和厂房进货口的一个传感器,它一旦收到进货口传感器信号,就会利用摄像头扫描所有的传送带,把那些有零件的传送带编号告诉工人。

厂房后来扩建了,生产线又扩大了,原来的监视器无法覆盖全部的传送带,工人调整了摄像头的位置和个数,改进的了这个问题。但是这个方案并不完善,摄像头移动需要时间,传送带上配件出现的时候,摄像头的位置需要移动很长时间才能完全覆盖所有传送带,找到那些有零件出现的传送带的编号(无差别轮训),工人并不能及时的相应。

为了提高效率,工人想了一个办法,每次增加一个传送带,都在传送带另一侧放上一个传感器,他把监控器和传感器直接相连,当有传感器感应到有配件的到达的时候,监视器直接把所有有配件出现的传感器编号告诉给工人,这样工人的相应更加及时,这个方案也保持着稳定的效率。

说了这么多白话,我们再返回计算机系统里来理解这些容易混淆的概念。当进程调用系统IO方法的时候,比如发起一个http请求,接受 对应的http response的时候,如果系统IO方法(recvfrom)是立即返回那么这就是一个非阻塞的IO。阻塞IO通常需要在数据就绪后还要将数据从内核拷贝到请求的进程空间,然后才会返回成功。

对于非阻塞IO,如果执行忙轮询的是进程,进程根据recvfrom返回结果来确认数据是否已经就绪。这种基于系统IO接口的调用,每次只能确认一个流上数据状态。 并没有实现一次能够返回多个流的数据状态信息。

select/poll/epoll这些系统级别实现的方法就是相关进程的一个代理,它们可以同时监视多个流的状态。它们的主要区别是实现方式和性能上的,简单总结如下:

  • select 能够监听的描述是有限的,并且因为需要遍历所有添加的描述符,性能上也是有损耗的。

  • poll 虽然监听数量不受打开描述符号限制,但是由于和select一样基于无差别轮询的方式,在大量连接,少数就绪的情况下,I/O的性能会有比较明显的下降。

  • epoll 支持的文件描述符上限是最大可以打开文件的数目, 并且基于事件单独注册了回调函数,所以在处理延迟和响应不是特别及时的WAN网上特别合适。

同步/异步(IO)

讲完了阻塞/非阻塞,我们再来看看同步的概念,这里的同步和异步指的是IO层面上,请不要于应用内函数调用混淆。进程在访问系统IO接口的时候,虽然接口返回了,把数据从内核中读入到自己 内存空间中,这个过程依然是同步和阻塞的。所以I/O多路复用依然是同步IO

异步IO应该是从调用系统IO接口开始到最后数据拷贝,进程并没有参与,而是收到就绪通知后拿来就用。这种模型在实际场景中应用的比较少,感兴趣的同学,可以自行搜索。


欢迎大家留言讨论,或者加入我们的Python的QQ学习群(278529278)共同学习。