因为最近做项目,一直接触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_handler
, remove_handler
, update_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。