一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

python高性能web开发与测试实验实例

时间:2015-11-24 编辑:简简单单 来源:一聚教程网

python具有如下两个特征:

解释型语言

GIL全局解释器锁


前者导致其性能天然就被编译型语言在性能上落后了许多。而后者则在多核并行计算时代,极大的限制了python的应用场景。

但是通过合理的web框架,则可以使用python扬长避短,仍然能够在多核并行时代须保持其高效开发的生产力同时,在性能上也有出色表现。例如,tornado框架。

tornado框架主要做了如下几件事:

使用单线程的方式,避免线程切换的性能开销,同时避免在使用一些函数接口时出现线程不安全的情况

支持异步非阻塞网络IO模型,避免主进程阻塞等待

前人实验

基于python语言的web框架众多,但是主流的有“Django”和“Tornado”基本上可以代表了它们的实现理念。

因为本文的重点是对 同步 和 异步 进行对比。所以关于不同web框架的性能对比实验,就引用一位网友的帖子的实验结果吧。

参考文章 [1]:轻量级web server Tornado代码分析

此文章有些部分写得比较简略,但是我们先大胆的做一下假设,作者是使用不同的python的web框架对最基本的 HelloWorld 代码进行了实现。

参考的Tornado实现如下:

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

最后使用 Apache Benchmark (ab),在另外一台机器上使用了如下指令进行负载测试:

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

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


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

根据引文作者的观点:tornado是完虐其它的web框架的。

本文点评:此实验只是暂时让大伙建立一下宏观的对不同的web框架的性能的认识,至于可信度是存疑的,因为实验报告写得不太规范,细节省略太多。本文的观点是,如果都是采用同步的的写法,tornado和django的性能差异应该没有那么大的。当然这不太重要了,后面提到的 同步 和 异步 才是比较重要的。

下面则是本文的重点,同步和异步网络IO的性能测试和差异对比。

测试环境

环境

CPU:core i3

操作系统:Ubuntu 14.0

Python框架:py2.7

Web服务器:Tornado 4.2.0,服务器只启用一核心

内容

使用同步和异步的方式来写一段延时代码,然后再使用 apachebench进行压力测试:

并发量 40

总请求量 200

由于本文只是做性能对比,而不是性能的上限对比,所以都使用的是比较少的压力。

同步和异步代码

class SyncSleepHandler(RequestHandler):
    """
    同步的方式,一个延时1s的接口
    """
    def get(self):
        time.sleep(1)
        self.write("when i sleep 5s")
 
 
class SleepHandler(RequestHandler):
    """
    异步的延时1秒的接口
    """
    @tornado.gen.coroutine
    def get(self):
        yield tornado.gen.Task(
            tornado.ioloop.IOLoop.instance().add_timeout,
            time.time() + 1
        )
        self.write("when i sleep 5s")

同步测试结果

/  ab -n 200 -c 40 http://localhost:8009/demo/syncsleep-handler/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
 
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests
 
 
Server Software:        TornadoServer/4.2.1
Server Hostname:        localhost
Server Port:            8009
 
Document Path:          /demo/syncsleep-handler/
Document Length:        15 bytes
 
Concurrency Level:      40
Time taken for tests:   200.746 seconds
Complete requests:      200
Failed requests:        0
Total transferred:      42000 bytes
HTML transferred:       3000 bytes
Requests per second:    1.00 [#/sec] (mean)
Time per request:       40149.159 [ms] (mean)
Time per request:       1003.729 [ms] (mean, across all concurrent requests)
Transfer rate:          0.20 [Kbytes/sec] received
 
Connection Times (ms)
            min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       1
Processing:  1005 36235 18692.2  38133  200745
Waiting:     1005 36234 18692.2  38133  200745
Total:       1006 36235 18692.2  38133  200746
 
Percentage of the requests served within a certain time (ms)
50%  38133
66%  38137
75%  38142
80%  38161
90%  38171
95%  38176
98%  38179
99%  199742
100%  200746 (longest request)

异步测试结果

/  ab -n 200 -c 40 http://localhost:8009/demo/sleep-handler/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
 
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests
 
 
Server Software:        TornadoServer/4.2.1
Server Hostname:        localhost
Server Port:            8009
 
Document Path:          /demo/sleep-handler/
Document Length:        15 bytes
 
Concurrency Level:      40
Time taken for tests:   5.083 seconds
Complete requests:      200
Failed requests:        0
Total transferred:      42000 bytes
HTML transferred:       3000 bytes
Requests per second:    39.35 [#/sec] (mean)
Time per request:       1016.611 [ms] (mean)
Time per request:       25.415 [ms] (mean, across all concurrent requests)
Transfer rate:          8.07 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      0       2
Processing:  1001 1010  12.0   1005    1053
Waiting:     1001 1010  12.0   1005    1053
Total:       1001 1010  12.3   1005    1055
 
Percentage of the requests served within a certain time (ms)
  50%   1005
  66%   1009
  75%   1011
  80%   1015
  90%   1032
  95%   1044
  98%   1045
  99%   1054
 100%   1055 (longest request)

结果对比

在并发量为40,总请求量为200的简单的压力测试里面,两种网络IO模型的编程方式的性能对比如下:


同步和异步性能对比
性能指标同步阻塞式异步非阻塞式
每秒处理请求数(Requests per second)139
请求平均等待时间-ms(Time per request,mean)401491017
请求平均处理时间-ms(Time per request,across all )100325

测试的结果比较符合被测试程序的理论预期,因为被测试程序就功能就是:一个1s的延时等待。

显然:异步非阻塞式 和性能是远高于 同步阻塞式 的。

在上表中的 同步IO模型 数据里:只要是进入了单个请求的处理环节,进入到睡眠等待的 内核态 操作时,就会将整个进程给 阻塞,别的程序就只能进入 等待 状态了,这样本质上还是使用的 串行 的处理方式,所以 请求平均处理时间 大概是1000ms(1秒)左右,然后完成一个并发度为40的请求平均等待时间为40149ms。

关于上面参数的理解可以进行简单的类比解释。

以如下场景为例子:客户去银行处理业务的窗口办理业务。

并行度:银行开设的服务窗口数和前台服务员

对应CPU,窗口数对应着核心数,即真正的实现并行的能力,即不是在时间分片后交错进行的 “假象并行”

并发度:大厅里面所有服务窗口等待服务的人数

对应着单次的并发度,即本次作业需要处理的任务量

总请求量:从银行大厅外面陆续过来加入到大厅队伍的客户的累计人数

内核态操作:银行业务中必须只能由前台服务员处理的操作

用户态操作:客户自己要处理的工作,比如:准备好自己的身份证,到外面复印证件,打电话和公司同事确认信息等等。

那么关于 同步 和 异步 的概念类比如下:

同步阻塞系统:银行 没有 排队叫号系统 ,客户(Web服务器进程) 只能 在队伍人群里面傻等轮到自己,没有在排队时间干其它事的机会。随着外面的人不断地进入大厅,新请求的每个人都要等前面的队伍的全部处理完毕后( 40149ms)才能等到业务员(CPU)花1003ms 来处理自己的业务

异步非阻塞系统:银行 有 排队叫号系统 ,客户有可以 不用 在拥挤的人群中傻等,旁边的休息区打开处理其它事情。客户直接领取叫号单据,花掉 5ms 递交准备材料(发起内核态操作请求) 要么收发邮件,要么看下小电影,然后等叫号系统叫自己后,立刻上去 20ms的时间解决掉问题。客户实际浪费在这上面的时间为 25ms ,当然银行业务员(CPU)还是要花 1000ms 去处理这个任务的

在这个假设的场景里面,不管是同步还是异步,业务员(CPU)都是 满负荷 的工作,但是却极大的节省了客户(web服务器进程) 的时间。这样客户自身可以把等待业务员响应的时间都利用起来做一些其它工作,这样就极大地提高了整体的工作效率。

众所周知,python有GIL,所以多线程其实是伪多线程。tornado于是就单进程只用单线程,不做线程切换,但是又要实现并行的方式,就全部使用异步了。只要是某个请求进入了内核态的耗时的IO操作,tornado的主进程在发起内核IO初始化之后就做不管它了,立刻回到web的监控中来去响应别的请求。等内核态的IO完成之后,再回调到用户态的主进程处理结果。如果是用同步模型,如果是使用单进程多线程,则会造成线程切换的开销,如果使用单进程单线程(像django一样),如果有一个请求比较耗时,第二个人的请求只会排队等候的,Web服务进程绝大多数情况都是被阻塞状态,性能就极大地降低了。

最后结合前面的延时1s的例子,再加一个即时响应的接口示例:

class JustNowHandler(tornado.web.RequestHandler):

def get(self):

self.write("i hope just now see you")

有兴趣的同学可以自己做实验。 事先约定:

同步延时1s的接口为:A

异步延时1s的接口为:B

即时响应的接口为:C

使用单核模式运行web服务器。

然后在浏览器中以不同的顺序组合运行程序请求接口:

先即时再延时

先C再A:总共是1s后响应完毕C和A,C立刻响应

先C再B:总共是1s后响应完毕C和B,C立刻响应

先延时再即时

先A再C:总共是1s后响应完毕C和A,C必须等A处理完毕后,才能在1s后响应

先B再C:总共是1s后响应完毕C和B,C能立刻响应

同步模型中,一旦进程被阻塞掉,那么程序的效率就被等待的时间给严重降低了。

总结

有兴趣的同学,可以更深入的研究一下 《Unix网络编程-卷1,套接字联网API》(W.Richard Stevens) 的第6章第2节 I/O模型。

在python的web框架里面,tornado就是采用的最高效的异步非阻塞框架,可以在python语言下提供高性能的web应用服务。



轻量级web server Tornado代码分析


最近在做项目,使用了Tornado这个用python写的由Facebook开源出来的web服务器框架。这确实是一个轻量级的框架,只需要几行代码,直接运行脚本,就可以建立起一个server了。Tornado使用了epoll方式,在linux环境下,用了epoll的,总是受到更多的关注,呵呵。这比我们销售短信项目中使用的c++ Poco库使用的poll模型效率要高一些。再加上python语言本身强大的脚本能力,灰度发布从代码行数上来讲,非常得少。没有括号而依赖缩进的规则,使得Tornado的源代码看起来也比较舒服,而且他们均出自牛人之手,确实是学习网络编程的好对象。

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

    Core web framework
        tornado.web ―RequestHandler and Application classes
        tornado.httpserver ― Non-blocking HTTP server
        tornado.template ― Flexible output generation
        tornado.escape ― Escaping and string manipulation
        tornado.locale ― Internationalization support
    Asynchronous networking
        tornado.ioloop ― Main event loop
        tornado.iostream ― Convenient wrappers for non-blocking sockets
        tornado.httpclient ― Non-blocking HTTP client
        tornado.netutil ― Miscellaneous network utilities
    Integration with other services
        tornado.auth ― Third-party login with OpenID and OAuth
        tornado.database ― Simple MySQL client wrapper
        tornado.platform.twisted ― Run code written for Twisted on Tornado
        tornado.websocket ― Bidirectional communication to the browser
        tornado.wsgi ― Interoperability with other Python frameworks and servers
    Utilities
        tornado.autoreload ― Automatically detect code changes in development
        tornado.gen ― Simplify asynchronous code
        tornado.httputil ― Manipulate HTTP headers and URLs
        tornado.options ― Command-line parsing
        tornado.process ― Utilities for multiple processes
        tornado.stack_context ― Exception handling across asynchronous callbacks
        tornado.testing ― Unit testing support for asynchronous code

今天主要和大家分享一下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 框架上(Django、web.py、CherryPy),针对最简单的 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)

热门栏目