Tornado之源码解析

因为最近做项目,一直接触tornado,所以抽空把源码的一些原理搞一搞。

tornado采用多进程+异步+epoll的模型,可以提供比较强大的网络响应性能。通过Nginx+tornado一起部署,可以同时支持多个实例的运行,从而支持加倍的请求响应,可达数千并发连接。

模块分析

tornado服务器主要三大处理模块,IOLoop,IOStream,HTTPConnection。

1. 先说说IOLoop初始化:

在Tornado服务器中,IOLoop是调度的核心模块,Tornado服务器把所有的socket描述符都注册到IOLoop, 注册的时候指明回调处理函数,IOLoop内部不断的监听IO事件, 一旦发现某个socket可读写, 就调用其注册时指定的回调函数。 IOLoop使用了单例模式。

在Tornado运行的整个过程中,只有一个IOLoop实例,仅需一个 IOLoop实例, 就可以处理全部的IO事件。上文中多次用到了ioloop.IOLoop.instance()这个方法。它会返回ioloop的一个单例。下面这段代码,可以看到python是怎么定义一个单例的。代码中使用了cls,这不是一个关键字,和self一样,cls是python的一个built-in变量,self表示类的实例,而cls表示类。大概代码如下(中间有所省略):

class IOLoop(object):
    def instance(cls):
        if not hasattr(cls, "_instance"):
            cls._instance = cls()
        return cls._instance

    def initialized(cls):
        return hasattr(cls, "_instance")

上文说到tornado是基于epoll事件驱动模型,也不完全正确,tornado实际上是根据平台选择底层驱动。请看IOLoop类的configurable_default方法:

@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

这里的IOLoop实际上是个通用接口,根据不同平台选择:linux->epoll,BSD->kqueue,如果epoll和kqueue都不支持则选择select(性能要差些)。

class IOLoop(Configurable):IOLoop 继承了Configurable类,Configurable类的__new__方法调用了configured_class方法:

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

configured_class方法又调用了configurable_default方法:

@classmethod
    def configured_class(cls):
        # type: () -> type
        """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

所以当初始化一个IOLoop实例的时候就给IOLoop做了配置,根据不同平台选择合适的驱动。

2. 说说epoll的接口(只分析Linux平台)

class _EPoll(object):
    _EPOLL_CTL_ADD = 1  # 添加一个新的epoll事件
    _EPOLL_CTL_DEL = 2  # 删除一个epoll事件
    _EPOLL_CTL_MOD = 3  # 改变一个事件的监听方式

    def __init__(self):
        self._epoll_fd = epoll.epoll_create()
    def fileno(self):
        return self._epoll_fd
    def register(self, fd, events):
       epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_ADD, fd, events)
    def modify(self, fd, events):
        epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_MOD, fd, events)
    def unregister(self, fd):
        epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_DEL, fd, 0)
    def poll(self, timeout):
        return epoll.epoll_wait(self._epoll_fd, int(timeout * 1000))

三种操作(添加,删除,改变)分别对应tornado.IOLoop里面的三个函数:add_handlerremove_handlerupdate_handler

def add_handler(self, fd, handler, events):
    fd, obj = self.split_fd(fd)
    self._handlers[fd] = (obj, stack_context.wrap(handler))
    self._impl.register(fd, events | self.ERROR)
 
def update_handler(self, fd, events):
    fd, obj = self.split_fd(fd)
    self._impl.modify(fd, events | self.ERROR)
 
def remove_handler(self, fd):
    fd, obj = self.split_fd(fd)
    self._handlers.pop(fd, None)
    self._events.pop(fd, None)
    try:
        self._impl.unregister(fd)
    except Exception:
        gen_log.debug("Error deleting fd from IOLoop", exc_info=True)

上面的self._impl就是 select.epoll(),使用方法可以参考epoll接口。

3. 说说事件驱动

IOLoop的start()方法用于启动事件循环(Event Loop)。

    def start(self):
        if self._stopped:
            self._stopped = False
            return

        self._running = True
        while True:
            poll_timeout = 0.2
            callbacks = self._callbacks
            self._callbacks = []

            for callback in callbacks:
                self._run_callback(callback)
            try:
                event_pairs = self._impl.poll(poll_timeout)
            except Exception, e:
                if (getattr(e, 'errno', None) == errno.EINTR or
                    (isinstance(getattr(e, 'args', None), tuple) and
                     len(e.args) == 2 and e.args[0] == errno.EINTR)):
                    continue
                else:
                    raise

            if self._blocking_signal_threshold is not None:
                signal.setitimer(signal.ITIMER_REAL,
                                 self._blocking_signal_threshold, 0)
                self._events.update(event_pairs)
 
            while self._events:
                fd, events = self._events.popitem()
                self._handlers[fd](fd, events)
        self._stopped = False
        if self._blocking_signal_threshold is not None:
            signal.setitimer(signal.ITIMER_REAL, 0, 0)

大致的思路是:有连接进来(client端请求),就丢给epoll,顺便注册一个事件和一个回调函数,我们主线程还是继续监听请求;然后在事件循环中,如果发生了某种事件(如socket可读,或可写),则调用之前注册的回调函数去处理。

4.  其他有关tornado 异步也可以研究下tornado-celery。

发布了74 篇原创文章 · 获赞 43 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/fenglei0415/article/details/84029012
今日推荐