Tornado

Tornado是一个用python编写的一个强大的、可扩展的异步HTTP服务器,同时也是一个web开发框架。tornado是一个非阻塞式web服务器,其速度相当快。得利于其非阻塞的方式和对 epoll的运用,tornado每秒可以处理数以千计的连接,这意味着对于实时web服务来说,tornado是一个理想的web框架。它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中。

主要和Django为代表的传统框架进行比较,这一类的Python web应用部署的时候一般是采用WSGI协议与服务器对接的,而这类服务器通常是基于多线程/多进程的,也就是说每有一个网络请求,服务器都会有一个线程/进程进行处理。

WSGI是个同步模型,不支持非阻塞的请求方式,Tornado默认是不推荐使用WSGI的,如果在Tornado中使用WSGI,将无法使用Tornado的异步非阻塞的处理方式,相应的异步接口也就无法使用,性能方面也就大打折扣,这个也是Tornado性能如此优越的原因。

Tornado 底层

处理多连接高并发的思路

第一种:传统的循环遍历的方式处理多个连接

这种方式明显的缺点就是,当其中任何一个socket的文件数据不ready的时候,线程/进程会一直等待,进而导致后面要处理的连接都被阻塞,整个应用也就阻塞了。

第二种:select技术

select本身是一个系统调用函数

 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct  
 timeval  *timeout);

实现过程:

  1. 首先fdset集合里需要监控的文件句柄由程序员来添加,当前连接需要监控哪些文件句柄,那么通过FD_SET宏来进行添加。

  2. 然后调用select()函数将fd_set从用户空间拷贝到内核空间。

  3. 注册一个回调函数。

  4. 内核对文件句柄进行监控。

  5. 当有满足可读写等条件时/超时调用回调函数并将文件句柄集合拷贝回用户空间。

  6. 应用通过轮询的方式查找所有文件句柄,用FD_ISSET宏来判读具体是哪个文件句柄可操作。

  7. 当再次有新连接处理需要监控,再次重复以上步骤往内核拷贝fdset。

缺点分析:

  1. 句柄上限:单个进程通过轮询的方式监控所有的文件句柄,当文件句柄越多,处理的效率越低,为了保证效率,文件句柄也就设置了上限,这个上限和内存也是又一定关系的,32位机默认是1024。

  2. 重复初始化:每次监控都重复将fdset从用户空间拷贝到内核空间,然后又从内核空间拷贝到用户空间,这个过程重复比较耗费系统资源。

  3. 逐个排查文件效率不高: 检测哪些文件句柄可操作时,采用的是轮询遍历所有的文件句柄,用FD_ISSET宏来判断文件句柄是否可操作,然而实际情况,大部分文件句柄是不可操作的,这种逐个排查的方式效率太低。

第三种:poll技术

poll技术与select 技术本质上是没有区别的,只是文件句柄的存储结构变更了,变成了链表,所以没有了文件句柄的上限,但是其他缺点依旧存在。

第四种 :epoll技术(重点,也是tornado的核心)

epoll技术整个流程其实和select、poll技术大体上是一样的,主要是针对造成效率低下的点进行优化,可以说是将select和poll技术的缺点一一解决才达到现在的高效率,接下来我们一一道来:

  1. 句柄上限 句柄上限的问题poll技术已经解决,就不用多说了。

  2. 重复初始化 这个问题就像中学时候读书书包带课本一样(中学的课程数量和书本数量之多大家应该都懂的),每天上学把所有几十本课本从家里背到学校,放学了再从学校将所有书背回家,但你今天家庭作业实际需要带的书可能就个别课程的个别几本书而已。

所以为了减轻我们身体的负担,是不是放学的时候只带几本今天需要做家庭作业的几本书就很轻松了,同样的为了减少重复初始化过程中用户空间和内核空间发生不必要的拷贝带来的资源浪费,epoll技术提供了epoll_ctl函数,在用epoll_ctl函数进行事件注册的时候,会将文件句柄都复制到内核中,所以不用每次都复制一遍,当有新的文件句柄时采用的也是增量往内核拷贝,确保了每个文件句柄只会被拷贝一次。

  1. 逐个排查文件效率不高

epoll会用epoll_ctl为每个文件句柄注册一个回调函数,同时会在内核中通过epoll_create创建一个专用链表(还有包含存储fd的专用内存空间),当有文件句柄状态发生变更,通过回调函数会将状态发生变更的文件句柄加入该链表,epoll技术还提供了epoll_wait函数,来查看链表中有没有就绪的文件句柄,然后只将该链表中的就绪文件句柄从内核空间拷贝到用户空间,这样一来就不用遍历每个文件句柄,只处理状态发生变更的,效率自然就提升上去了。

总结一下,epoll技术提供了三个系统调用函数:

  • epoll_create:用于创建和初始化一些内部使用的数据结构。
  • epoll_ctl: 用于注册时间、添加、删除和修改指定的df及其期待的事件。
  • epoll_wait: 用于等待先前指定的fd事件,即就绪的fd。

通过以上三点解决方案,epoll技术的效率相比select、poll技术效率大大提升了,Tornado自然也采用了epoll技术,通过这种技术也就解决了著名的C10k问题,实现了用一个进程/线程来同时处理若干个连接的想法,减少了硬件资源的浪费。

请求的生命周期

  1. 首先Tornado需要建立监听,会创建一个socket用于监听,如果有客户端A请求建立连接之后,Tornado会基于原先的socket新创建一个包含客户端A连接的有关信息的socket(分配新的监听端口),用于监听和客户端A的请求。此时对Tornado来说就有两个socket需要进行监控,原先的socket继续用来监听建立新连接,新的socket用于和客户端A进行通信,假如没有epoll技术的话,Tornado需要自己去循环询问哪个socket有新的请求。

  2. 有了epoll技术,Tornado只需要把所有的socket丢给epoll,epoll作为管家帮忙监控,然后Torando.ioloop.IOLoop.current().start()开启循环,不断的去询问epoll是否有请求需要处理,这就是ioloop所做的工作,也是Tornado的核心部分。

  3. 当有客户端进行请求,epoll就发现有socket可处理,当ioloop再次询问epoll时,epoll就把需要处理的socket交由Tornado处理

  4. Tornado对请求进行处理,取出报文,从报文中获取请求路径,然后从tornado.web.Applcation里配置的路由映射中把请求路径映射成对应的处理类

  5. 处理类处理完成后,生成响应,将响应内容封装成http报文,通过请求时建立的连接(尚未中断)将响应内容返回给客户端。

  6. 当有多个请求同时发生,Tornado会按顺序挨个处理。

Tornado的应用场景

要性能,Tornado 首选;要开发速度,Django 和Flask 都行,区别是Flask 把许多功能交给第三方库去完成了,因此Flask 更为灵活。Django适合初学者或者小团队的快速开发,适合做管理类、博客类网站、或者功能十分复杂需求十分多的网站,Tornado适合高度定制,适合访问量大,异步情况多的网站。