tornado

参考:http://www.nowamagic.net/academy/detail/13321002

 

Tornado就是我们在FriendFeed的Web服务器及其常用工具的开源版本。Tornado和现在的主流Web服务器框架(包括大多数Python的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。它还实现了一个同样简洁的模板框架。Tornado在自己的异步网络访问框架之上完整地实现了HTTP服务器,这也是它的核心框架的重要部分。

我觉得对非阻塞IO (nonblocking IO)或异步IO (asynchronous IO, AIO)很有必要谈一谈。如果你已经完全知道他们是什么了,可以跳去看下一节。我尽可能的使用一些例子来说明它们是什么。

让我们假设你正在写一个需要请求一些来自其他服务器上的数据(比如数据库服务,再比如新浪微博的open api)的应用程序,然后呢这些请求将花费一个比较长的时间,假设需要花费5秒钟。大多数的web开发框架中处理请求的代码大概长这样:

def handler_request(self, request):
    answ = self.remote_server.query(request) # this takes 5 seconds
    request.write_response(answ)

如果这些代码运行在单个线程中,你的服务器只能每5秒接收一个客户端的请求。在这5秒钟的时间里,服务器不能干其他任何事情,所以,你的服务效率是每秒0.2个请求,哦,这太糟糕了。

当然,没人那么天真,大部分服务器会使用多线程技术来让服务器一次接收多个客户端的请求,我们假设你有20个线程,你将在性能上获得20倍的提高,所以现在你的服务器效率是每秒接受4个请求,但这还是太低了,当然,你可以通过不断地提高线程的数量来解决这个问题,但是,线程在内存和调度方面的开销是昂贵的,我怀疑如果你使用这种提高线程数量的方式将永远不可能达到每秒100个请求的效率。

如果使用AIO,达到每秒上千个请求的效率是非常轻松的事情。服务器请求处理的代码将被改成这样:

def handler_request(self, request):
    self.remote_server.query_async(request, self.response_received)

def response_received(self, request, answ): # this is called 5 seconds later
    request.write(answ)

AIO的思想是当我们在等待结果的时候不阻塞,转而我们给框架一个回调函数作为参数,让框架在有结果的时候通过回调函数通知我们。这样,服务器就可以被解放去接受其他客户端的请求了。

然而这也是AIO不太好的地方:代码有点不直观了。还有,如果你使用像Tornado这样的单线程AIO服务器软件,你需要时刻小心不要去阻塞什么,因为所有本该在当前返回的请求都会像上述处理那样被延迟返回。

关于异步IO,比当前这篇过分简单的介绍更好的学习资料请看 The C10K problem

Tornado设计模型概览

在深入到模块进行分析之前,首先来看看Tornado的设计模型。

/media/note/2013/02/09/tornado-core/fig1.png

Tornado框架设计模型

Tornado不仅仅是一个WEB框架,它还完整地实现了HTTP服务器和客户端,在此基础上提供WEB服务。它可以分为四层:最底层的EVENT层处理IO事件;TCP层实现了TCP服务器,负责数据传输;HTTP/HTTPS层基于HTTP协议实现了HTTP服务器和客户端;最上层为WEB框架,包含了处理器、模板、数据库连接、认证、本地化等等WEB框架需要具备的功能。

主要模块

  • 核心Web框架
    • tornado.web - RequestHandlerApplication
    • tornado.httpserver - 非阻塞式HTTP服务器
    • tornado.template - 灵活的输出生成器
    • tornado.escape - 转义和字符串处理
    • tornado.locale - 国际化支持
  • 异步网络
    • tornado.ioloop - 核心的事件循环
    • tornado.iostream - 对非阻塞式的socket的简单封装,以方便常用读写操作
    • tornado.httpclient - 非阻塞式HTTP客户端
    • tornado.netutil - 各种网络工具
  • 和其他服务的集成
    • tornado.auth - OpenID和OAuth的第三方认证登录
    • tornado.database - 简单的MySQL客户端封装
    • tornado.platform.twisted - Tornado上的Twisted运行代码
    • tornado.websocket - 和浏览器的双向通信
    • tornado.wsgi - 和其他Python框架和服务器的协作
  • 工具
    • tornado.autoreload - 在开发中自动检测代码更改
    • tornado.gen - 简单的异步代码
    • tornado.httputil - 处理HTTP头和URL
    • tornado.options - 命令行解析
    • tornado.process - 多进程工具
    • tornado.stack_context - 异步回调中的异常处理
    • tornado.testing - 异步代码的单元测试支持

为了方便,约定$root指带tornado的根目录。总的来说,要用 Tornado 完成一个网站的构建,其实主要需要以下几个文件:

  • $root/tornado/web.py
  • $root/tornado/httpserver.py
  • $root/tornado/tcpserver.py
  • $root/tornado/ioloop.py
  • $root/tornado/iostream.py
  • $root/tornado/platfrom/epoll.py
  • $root/app.py

另外可能还需要一些功能库的支持而需要引入的文件就不列举了,比如util和httputil之类的。来看看每个文件的作用。

  1. app.py 是自己写的,内容就如 tornado 的 readme 文档里给的示例一样,定义路由规则和 handler,然后创建 application,发起 server 监听,服务器就算跑起来了。
  2. 紧接着就是 web.py。其中定义了 Application 和 RequestHandler 类,在 app.py 里直接就用到了。Application 是个单例,总揽全局路由,创建服务器负责监听,并把服务器传回来的请求进行转发(__call__)。RequestHandler 是个功能很丰富的类,基本上 web 开发需要的它都具备了,比如redirect,flush,close,header,cookie,render(模板),xsrf,etag等等
  3. 从 web 跟踪到 httpserver.py 和 tcpserver.py。这两个文件主要是实现 http 协议,解析 header 和 body, 生成request,回调给 appliaction,一个经典意义上的 http 服务器(written in python)。众所周知,这是个很考究性能的一块(IO),所以它和其它很多块都连接到了一起,比如 IOLoop,IOStream,HTTPConnection 等等。这里 HTTPConnection 是实现了 http 协议的部分,它关注 Connection 嘛,这是 http 才有的。至于监听端口,IO事件,读写缓冲区,建立连接之类都是在它的下层--tcp里需要考虑的,所以,tcpserver 才是和它们打交道的地方,到时候分析起来估计很麻烦
  4. 先说这个IOStream。顾名思义,就是负责IO的。说到IO,就得提缓冲区和IO事件。缓冲区的处理都在它自个儿类里,IO事件的异步处理就要靠 IOLoop 了。
  5. 然后是 IOLoop。如果你用过 select/poll/epoll/libevent 的话,对它的处理模型应该相当熟悉。简言之,就是一个大大的循环,循环里等待事件,然后处理事件。这是开发高性能服务器的常见模型,tornado 的异步能力就是在这个类里得到保证的
  6. 最后是 epoll.py。其实这个文件也没干啥,就是声明了一下服务器使用 epoll。选择 select/poll/epoll/kqueue 其中的一种作为事件分发模型,是在 tornado 里自动根据操作系统的类型而做的选择,所以这几种接口是一样的(当然效率不一样),出于简化,直接就epoll吧^_^
  7. PS。如果你是一个细节控,可能会注意到 tornado 里的回调 callback 函数都不是直接使用的,而是使用 stack_context.wrap 进行了封装。但据我观察,封装前后没多大差别(指逻辑流程),函数的参数也不变。但根据它代码里的注释,这个封装还是相当有用的:

Use this whenever saving a callback to be executed later in a different execution context (either in a different thread or asynchronously in the same thread).

tornado网络部分最核心的两个模块就是ioloop.py与iostream.py,我们主要分析的就是这两个部分。

  • ioloop.py 主要的是将底层的epoll或者说是其他的IO多路复用封装作异步事件来处理。
  • iostream.py主要是对于下层的异步事件的进一步封装,为其封装了更上一层的buffer(IO)事件。

我们先来看看 ioloop(文档地址:http://www.tornadoweb.org/en/stable/ioloop.html):

We use epoll (Linux) or kqueue (BSD and Mac OS X) if they are available, or else we fall back on select(). If you are implementing a system that needs to handle thousands of simultaneous connections, you should use a system that supports either epoll or kqueue.

程序中主函数通常调用 tornado.ioloop.IOLoop.instance().start() 来启动IOLoop,但是看了一下 IOLoop 的实现,start 方法是这样的:

def start(self):
    """Starts the I/O loop.

    The loop will run until one of the callbacks calls `stop()`, which
    will make the loop stop after the current event iteration completes.
    """
    raise NotImplementedError()

也就是说 IOLoop 是个抽象的基类,具体工作是由它的子类负责的。如果是 Linux 平台,所以应该用 Epoll,对应的类是 PollIOLoop。PollIOLoop 的 start 方法开始了事件循环。

问题来了,tornado.ioloop.IOLoop.instance() 是怎么返回 PollIOLoop 实例的呢?刚开始有点想不明白,后来看了一下 IOLoop 的代码就豁然开朗了。

class IOLoop(Configurable):

IOLoop 继承自 Configurable,后者位于 tornado/util.py。

A configurable interface is an (abstract) class whose constructor acts as a factory function for one of its implementation subclasses. The implementation subclass as well as optional keyword arguments to its initializer can be set globally at runtime with configure.

Configurable 类实现了一个工厂方法,也就是设计模式中的“工厂模式”,看一下__new__函数的实现:

def __new__(cls, **kwargs):
    base = cls.configurable_base()
    args = {}
    if cls is base:
        impl = cls.configured_class()
        if base.__impl_kwargs:
            args.update(base.__impl_kwargs)
    else:
        impl = cls
    args.update(kwargs)
    instance = super(Configurable, cls).__new__(impl)
    # initialize vs __init__ chosen for compatiblity with AsyncHTTPClient
    # singleton magic.  If we get rid of that we can switch to __init__
    # here too.
    instance.initialize(**args)
    return instance

当创建一个Configurable类的实例的时候,其实创建的是configurable_class()返回的类的实例。

@classmethod
def configured_class(cls):
    """Returns the currently configured class."""
    base = cls.configurable_base()
    if cls.__impl_class is None:
        base.__impl_class = cls.configurable_default()
    return base.__impl_class

最后,就是返回的configurable_default()。此函数在IOLoop中的实现如下:

@classmethod
def configurable_default(cls):
    if hasattr(select, "epoll"):
        from tornado.platform.epoll import EPollIOLoop
        return EPollIOLoop
    if hasattr(select, "kqueue"):
        # Python 2.6+ on BSD or Mac
        from tornado.platform.kqueue import KQueueIOLoop
        return KQueueIOLoop
    from tornado.platform.select import SelectIOLoop
    return SelectIOLoop

EPollIOLoop 是 PollIOLoop 的子类。至此,这个流程就理清楚了。



未完,待续。。。。。。。。。。。。。。。。。。。。。。

posted on 2017-03-25 11:19  simple_孙  阅读(427)  评论(0编辑  收藏  举报

导航