Tornado代码分析

转自:http://blog.csdn.net/goldlevi/article/details/7047726

Tornado 采用多进程 + 非阻塞 + epoll的模型,可以提供比较强大的网络响应性能。在我们的项目中,单个实例的灰度发布server就可以支持每秒1500次的请求响应。而通过 Nginx与tornado一起部署,可以同时支持多个实例的运行,从而支持加倍的请求响应,满足当前旺旺用户的升级需求。下图是旺旺灰度发布的架构图:

现在把Tornado里面的部分内容,以及一些重要的资料分享给大家,有兴趣的同学可以玩一下。

1 Tornado来历

Tornado 是一个开源的网络服务器框架,该平台基于社交聚合网站FriendFeed的实时信息服务开发而来。2007年,4名谷歌前软件工程 师一起创办了FriendFeed,旨在使用户能方便地跟踪好友在Facebook和Twitter等多个社交网站上的活动。结果两年 后,Facebook宣布收购FriendFeed,这一交易的价格约为5000万美元。而此时,FriendFeed只有12名员工。据说这帮人后来又 到了Google,搞出了现在的Google App Engine ……

Tornado由Python编写,跟其他主流的Web服务器框架不同是采用epoll非阻塞IO,响应快速,可处理数千并发连接,特别适用用于实时的Web服务。Tornado当前版本为2.1.1,官方网站为http://www.tornadoweb.org/,有兴趣的同学可以去尝试一下。

2 Tornado简介

Tornado主要包含了如下四部分内容。官方的帮助文档,实际上只是源码注释的集合。大家直接看源码就可以了。

今天主要和大家分享一下HTTP SERVER的相关内容。

2.1Tornado HTTP SERVER

使用Tornado可以很方便地架构出各种类型的web服务器。我们现在从HTTP服务器入手,来看一下它的实现。下面这张图大家应该见得很多了,是所有web server的一般工作方式。

l  服务器端bind到一个端口,然后开始listen。

l  客户端connect上来以后,将请求发送给服务端。

l  服务端处理完成后返回给客户端。

这样,一个请求就处理结束了。不过,当需要处理成千上万的连接的时候,我们就会在这个基础上考虑更多的情况。这也就是大家熟悉的The C10K problem。一般大家会有如下一些选择:

l  一个线程服务多个客户端,使用非阻塞I/O和水平触发的就绪通知

l  一个线程服务多个客户端,使用非阻塞I/O和就绪改变时通知

l  一个服务线程服务多个客户端,使用异步I/O

l  一个服务线程服务一个客户端,使用阻塞I/O

l  把服务代码编译进内核

Tornado采用的就是:多进程 + 非阻塞 + epoll模型

下面这张图基本上就显示了Tornado与网络相关的所有内容了:

2.2 第一个HTTP server例子

下面是官网提供的一个hello world的代码示范。

import tornado.ioloop

import tornado.web

class MainHandler(tornado.web.RequestHandler):

def get(self):

self.write("Hello, world")

application = tornado.web.Application([

(r"/", MainHandler),

])

if __name__ == "__main__":

application.listen(8888)

tornado.ioloop.IOLoop.instance().start()

实 现非常简单, 只需要定义自己的处理方法, 其它的东西全部交给Tornado完成。首先创建web application, 并把我们的处理方法MainHandler传递过去。然后在8888开始监听。最后启动事件循环, 开始监听网络事件,主要是socket的读和写。Python又是这样一种便捷的语言,上面这段代码直接贴到文本中,无需编译,就可以直接运行,一个 server就产生了。

2.3 模块分析

我们接下来将逐个分析这部分代码。首先对Tornado有个全面的了解。Tornado服务器有3大核心模块:

(1) IOLoop

从上面的代码可能看出,Tornado为了实现高并发和高性能, 使用了一个IOLoop来处理socket的读写事件, IOLoop基于epoll, 可以高效的响应网络事件. 这是Tornado高效的保证.

(2) IOStream

为了在处理请求的时候, 实现对socket的异步读写, Tornado实现了IOStream类, 用来处理socket的异步读写。

(3) HTTPConnection

这个类用来处理http的请求,包括读取http请求头,读取post过来的数据,调用用户自定义的处理方法,以及把响应数据写给客户端socket。

下面这幅图描述了tornado服务器的大体处理流程, 接下来我们将会详细分析每一步流程的实现。

3 源码分析

3.1 bind和listen

服务器的第一步就是bind。Httpserver.py的bind函数可以看到一个标准的服务器启动过程:

def bind(self, port, address=None, family=socket.AF_UNSPEC):

if address == "":

address = None

// 查找网卡信息

for res in socket.getaddrinfo(address, port, family, socket.SOCK_STREAM,

0, socket.AI_PASSIVE | socket.AI_ADDRCONFIG):

af, socktype, proto, canonname, sockaddr = res

sock = socket.socket(af, socktype, proto)

flags = fcntl.fcntl(sock.fileno(), fcntl.F_GETFD)

flags |= fcntl.FD_CLOEXEC

fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, flags)

sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

if af == socket.AF_INET6:

if hasattr(socket, "IPPROTO_IPV6"):

sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)

sock.setblocking(0)

// bind和listen

sock.bind(sockaddr)

sock.listen(128)

self._sockets[sock.fileno()] = sock

// 加入io_loop

if self._started:

self.io_loop.add_handler(sock.fileno(), self._handle_events,

ioloop.IOLoop.READ)

for循环保证对每张网卡上的请求都得到监听。对于每个网卡,先建立socket,然后bind listen,最后将socket加入到io_loop, 注册的事件是ioloop.IOLoop.READ,也就是读事件。程序中还添加了对ipv6的处理。回调函数为_handle_events, 一旦listen socket可读, 说明客户端请求到来, 然后调用_handle_events接受客户端的请求。接下来,看一下_handle_events是怎么处理的。

3.2 accept

接上一节,Httpserver.py的_handle_events函数实现了accept的过程。代码如下:

def _handle_events(self, fd, events):

while True:

try:

connection, address = self._sockets[fd].accept()

except socket.error, e:

if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):

return

raise

if self.ssl_options is not None:

//这里有一段处理ssl的代码,比较长,省略

try:

stream = iostream.IOStream(connection, io_loop=self.io_loop)

HTTPConnection(stream, address, self.request_callback,

self.no_keep_alive, self.xheaders)

except:

logging.error("Error in connection callback", exc_info=True)

accept 方法返回客户端的socket, 以及客户端的地址。然后创建IOStream对象, 用来处理socket的异步读写. 这一步会调用ioloop.add_handler把client socket加入ioloop,再然后创建HTTPConnection, 处理用户的请求。接下来,我们看下iostream和httpconnection。

3.3 iostream

为 了实现对client socket的异步读写, 需要为client socket创建两个缓冲区: _read_buffer和_write_buffer,这样我们就不用直接读写socket,进而实现异步读写。这些操作都封装在IOStream类 中。概括来说,IOStream对socket的读写做了一层封装,通过使用两个缓冲区,实现对socket的异步读写。

IOStream与socket是一一对应的, 在iosteram.py可以找到iosteram的init方法:

def __init__(self, socket, io_loop=None, max_buffer_size=104857600,

read_chunk_size=4096):

self.socket = socket

self.socket.setblocking(False)

self.io_loop = io_loop or ioloop.IOLoop.instance()

        self._read_buffer = collections.deque()

        self._write_buffer = collections.deque()

self._state = self.io_loop.ERROR

with stack_context.NullContext():

self.io_loop.add_handler(

self.socket.fileno(), self._handle_events, self._state)

可以看到,初始化的时候建立了两个buffer,然后把自己的socket放到了io_loop。这样,当这个socket有读写的时候,就会回调到注册的事件self._handle_events里面了。_handle_events就很容易理解了,代码如下:

def _handle_events(self, fd, events):

if not self.socket:

logging.warning("Got events for closed stream %d", fd)

return

try:

if events & self.io_loop.READ:

self._handle_read()

if events & self.io_loop.WRITE:

 self._handle_write()

if events & self.io_loop.ERROR:

self.io_loop.add_callback(self.close)

return

state = self.io_loop.ERROR

if self.reading():

state |= self.io_loop.READ

if self.writing():

state |= self.io_loop.WRITE

if state != self._state:

self._state = state

self.io_loop.update_handler(self.socket.fileno(), self._state)

except:

logging.error("Uncaught exception, closing connection.",

exc_info=True)

self.close()

raise

3.4 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表示类。所以大家看了几个函数会发现,python的成员函数的第一个参数,不是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")

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)

这里的poll根据不同的系统环境,支持select、epoll和KQueue三种模式。下面是epoll模式的处理:

class _EPoll(object):

_EPOLL_CTL_ADD = 1

_EPOLL_CTL_DEL = 2

_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))

4 性能比较

这是一段官网上的描述:

“一个 Web 应用的性能表现,主要看它的整体架构,而不仅仅是前端的表现。和其它的 Python Web 框架相比,Tornado 的速度要快很多。我们在一些流行的 Python Web 框架上(Djangoweb.pyCherryPy), 针对最简单的 Hello, world 例子作了一个测试。对于 Django 和 web.py,我们使用 Apache/mod_wsgi 的方式来带,CherryPy 就让它自己裸跑。这也是在生产环境中各框架常用的部署方案。对于我们的 Tornado,使用的部署方案为前端使用nginx 做反向代理,带动 4 个线程模式的 Tornado,这种方案也是我们推荐的在生产环境下的 Tornado 部署方案(根据具体的硬件情况,我们推荐一个 CPU 核对应一个 Tornado 伺服实例,我们的负载测试使用的是四核处理器)。我们使用 Apache Benchmark (ab),在另外一台机器上使用了如下指令进行负载测试:

ab -n 100000 -c 25 http://10.0.1.x/

在 AMD Opteron 2.4GHz 的四核机器上,结果如下图所示:

在我们的测试当中,相较于第二快的服务器,Tornado 在数据上的表现也是它的 4 倍之多。即使只用了一个 CPU 核的裸跑模式,Tornado 也有 33% 的优势。”

使用同样的参数,对旺旺灰度发布服务器测试结果如下:

ab -n 20000 -c 50 ‘http://10.20.147.160:8080/redirect?uid=cnalichntest&ver=6.05.10&ctx=alitalk&site=cnalichn‘

配置nginx + 1个tornado服务器的时候:Requests per second:    672.55 [#/sec] (mean)

配置nginx + 4个tornado服务器的时候:Requests per second:    2187.45 [#/sec] (mean)

时间: 2024-10-20 09:46:32

Tornado代码分析的相关文章

Tornado 代码说明

1 class FaultyInfoHandler(tornado.web.RequestHandler): 2 def get(self): 3 import xmlrpc.client 4 s = xmlrpc.client.ServerProxy(LocalRpcSvcAddr) 5 info = s.faultyinfo() 6 self.render("faultyinfo.htm", lists=info) 7 8 def post(self): 9 import xmlr

java代码分析及分析工具

java代码分析及分析工具 一个项目从搭建开始,开发的初期往往思路比较清晰,代码也比较清晰.随着时间的推移,业务越来越复杂.代码也就面临着耦合,冗余,甚至杂乱,到最后谁都不敢碰. 作为一个互联网电子商务网站的业务支撑系统,业务复杂不言而喻.从09年开始一直沿用到现在,中间代码经过了多少人的手,留下了多少的坑,已经记不清楚了,谁也说不清了. 代码的维护成本越来越高.代码已经急需做调整和改善.最近项目组专门设立了一个小组,利用业余时间做代码分析的工作,目标对核心代码进行分析并进行设计重构. 代码分析

Java静态代码分析工具Infer

Java静态代码分析工具Infer 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.Infer介绍 Infer是Facebook最新开源的静态程序分析工具,用于在发布移动应用之前对代码进行分析,找出潜在的问题.目前Facebook使用此工具分析Facebook的App,包括Android.iOS.Facebook Messenger和Instagram等. Facebook称该工具帮助其每个月检查出应用潜在的数百个Bug,例如一些空指针访问.资源

$*和[email protected]之间区别代码分析

#!/bin/bash set 'apple pie' pears peaches for i in $*           /*单引号被去掉,循环单个字符输出*/ do echo $i done [[email protected] Ex_14.02-14.31]# sh 14-14-1 apple pie pears peaches -------------------------------------------------------------- #!/bin/bash set

《linux 内核完全剖析》 keyboard.S 部分代码分析(key_map)

keyboard.S 部分代码分析(key_map) keyboard中间有这么一段,我一开始没看明白,究竟啥意思 key_map: .byte 0,27 .ascii "1234567890-=" .byte 127,9 .ascii "qwertyuiop[]" .byte 13,0 .ascii "asdfghjkl;'" .byte '`,0 .ascii "\\zxcvbnm,./" .byte 0,'*,0,32

20145234黄斐《网络对抗技术》实验四,恶意代码分析

恶意代码 概述 恶意代码是指故意编制或设置的.对网络或系统会产生威胁或潜在威胁的计算机代码.最常见的恶意代码有计算机病毒(简称病毒).特洛伊木马(简称木马).计算机蠕虫(简称蠕虫).后门.逻辑炸弹等. 特征: 恶意的目的,获取靶机权限.用户隐私等 本身是计算机程序,可以执行,并作用于靶机 通过执行发生作用,一般来说不运行是没问题的 恶意代码分析 在大多数情况下,进行恶意代码分析时,我们将只有恶意代码的可执行文件本身,而这些文件并不是我们人类可读的.为了了解这些文件的意义,你需要使用各种工具和技巧

20145326蔡馨熠《网络对抗》——恶意代码分析

20145326蔡馨熠<网络对抗>--恶意代码分析 1.实验后回答问题 (1)如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所以想监控下系统一天天的到底在干些什么.请设计下你想监控的操作有哪些,用什么方法来监控.. 需要监控什么? 系统中各种程序.文件的行为. 还需要注意是否会出现权限更改的行为. 注册表. 是否有可疑进程. 如果有网络连接的情况,需要注意这个过程中的IP地址与端口. 用什么来监控? 最先想到的肯定是使用wireshark抓包了,再进行进一步分析. Sysinternals

代码分析—“CA0052 没有选择要分析的目标”(VS2012)

情况: 1.未采用代码分析时程序正常编译 2.采用代码分析,会提示"没有选择分析目标"或"未加载制定版本的程序集"...的错误 分析: 是由于代码分析依赖程序集的强签名,包括版本 解决方案: 1.修改代码分析工具的配置项: FxCopCmd.exe.config里节点AssemblyReferenceResolveMode的Value值StrongName修改为StrongNameIgnoringVersion或None 2.修改当前分析的项目: .csproj增加

常用 Java 静态代码分析工具的分析与比较

转载自: http://www.oschina.net/question/129540_23043 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBugs,PMD,Jtest),最后从功能.特性等方面对它们进行分析和比较,希望能够帮助 Java 软件开发人员了解静态代码分析工具,并选择合适的工具应用到软件开发中. 引言 在 Java 软件开发过程中,开发团队往往要花费大量的时间和精力发现并修改代