Python 和 PHP 的 Web 服务器性能测试

标签: python php web | 发表时间:2011-09-05 22:55 | 作者:(author unknown) 非狐外传
出处:http://simple-is-better.com/

这几天一直在玩虚拟机,测了几种Python和PHP的Web服务器的性能,顺便记录下来。

测试环境:

宿主:MacBook Pro MC700
操作系统:Mac OS X 10.6.8
CPU: 2.3GHz Intel Core i5(双核)
内存:4 GB

虚拟机:Virtual Box
操作系统:Linux version 2.6.32-5-686 (Debian 2.6.32-35)
CPU: 限制到40%宿主CPU,单核(2.3 * 40% = 0.92GHz)
内存:128 MB

数据库:MySQL 5.5.15(基本上是用small配置,禁用了InnoDB以节省内存)

Python:2.6.6
gevent:1.0a2
uWSGI:0.9.9.1
Tornado:2.0
amysql (for gevent):2011-09-02(无版本号,只能按更新日期来算。支持MySQL 5.1,MySQL 5.5不能完全通过所有测试,但本文所用的测试没问题。)
MySQL for Python:1.2.3

PHP:5.3.8-1
spawn-fcgi:1.6.3
nginx:1.0.6

Hello world就不列出了,4个都差不多,大致是PHP > uWSGI > gevent > Tornado。
这里主要模拟比较现实的使用。
准备一个数据表,引擎为MyISAM,字段为id(主键),value(浮点数),随机生成100万条数据插入。
访问前执行“SET GLOBAL query_cache_size = 0;”,禁用查询缓存。
每次访问时生成1~999990之间的随机数r,查询id为r~r+10之间的行,将value相加。重复这个操作10次,即访问10次数据库。最后把这个结果输出,值应该在50左右。
因为是单CPU,所以都只采用1个处理进程。
在宿主上用ab -c 100 -n 1000来测试(100并发,共1000请求)。

数据库启动后约剩80MB内存,执行完一次测试后剩40MB,有60多MB缓存。

先是gevent,用amysql的同步方式查询:

from gevent.pywsgi import WSGIServer
import amysql
from random import randint

con = amysql.Connection()
con.connect ('127.0.0.1', 3306, 'root', '123', 'test')
con.query('SET GLOBAL query_cache_size = 0;')
   
def application(env, start_response):
    sum = 0
    for i in xrange(10):
        r = randint(1, 1000000 - 10)
        rs = con.query('SELECT value FROM test WHERE id >= %s LIMIT 10;', (r,))
        for j in rs.rows:
            sum += j[0]
    start_response('200 OK', [('Content-Type','text/plain')])
    return ['%6f' % sum]
   
if __name__ == '__main__':
    WSGIServer(('', 8089), application).serve_forever()

启动占用6.8MB内存,测试完占用8.3MB。
结果为154 QPS(每秒处理154个请求),无失败请求。

gevent + amysql的异步方式:

import gevent
from gevent.pywsgi import WSGIServer
import amysql
from random import randint

con = amysql.Connection()
con.connect ('127.0.0.1', 3306, 'root', '123', 'test')
con.query('SET GLOBAL query_cache_size = 0;')

def query(r):
    return con.query('SELECT value FROM test WHERE id >= %s LIMIT 10;', (r,))
   
def application(env, start_response):
    sum = 0
    queries = []
   
    for i in xrange(10):
        r = randint(1, 1000000 - 10)
        queries.append(gevent.spawn(query, r))

    for q in queries:
        rs = q.get()
        for j in rs.rows:
            sum += j[0]
            pass
    start_response('200 OK', [('Content-Type','text/plain')])
    return ['%6f' % sum]
   
if __name__ == '__main__':
    WSGIServer(('', 8088), application).serve_forever()

结果为149 QPS,无失败请求。看来在并发足够多时,异步没有帮助,反而因为要执行更多代码而变慢了。

gevent + MySQL for Python:

from gevent.pywsgi import WSGIServer
import MySQLdb
from random import randint

con = MySQLdb.connect(user='root', passwd='123', db='test')
cu = con.cursor()
cu.execute('SET GLOBAL query_cache_size = 0;')
   
def application(env, start_response):
    sum = 0
    for i in xrange(10):
        r = randint(1, 1000000 - 10)
        cu.execute('SELECT value FROM test WHERE id >= %s LIMIT 10;', (r,))
        for j in cu.fetchall():
            sum += j[0]
    start_response('200 OK', [('Content-Type','text/plain')])
    return ['%6f' % sum]
   
if __name__ == '__main__':
    WSGIServer(('', 8087), application).serve_forever()

结果为122 QPS,无失败请求。这个库已经有1年多没更新了,看来已经不够给力了。

虽然测试是用的很快的主键查询,但我主要是测试访问数据库和处理响应的速度,数据库的性能不是我关注的重点。
可以看出amysql的同步方式是最好的,所以下面就只用它来测试。

uWSGI:
自带了HTTP服务器,因此直接运行“uwsgi -l 1000 --http :9090 --wsgi-file test.py &”即可。
因为数据库连接和生成application的代码都和第一个例子相同,只是无需运行server,就不重复列出了。
共2个进程,启动占用6.1+2MB,测试完占用6.2+2MB。
结果为134 QPS,无失败请求。很奇怪它的表现比hello world差很多,但内存控制还算不错,而且命令很方便。

Tornado:

import tornado.ioloop
import tornado.web
import amysql
from random import randint

con = amysql.Connection()
con.connect ('127.0.0.1', 3306, 'root', '123', 'test')
con.query('SET GLOBAL query_cache_size = 0;')
   
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        sum = 0
        for i in xrange(10):
            r = randint(1, 1000000 - 10)
            rs = con.query('SELECT value FROM test WHERE id >= %s LIMIT 10;', (r,))
            for j in rs.rows:
                sum += j[0]
        self.write('%6f' % sum)
   
application = tornado.web.Application([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    application.listen(8000)
    tornado.ioloop.IOLoop.instance().start()

启动占用10MB内存,测试完占用14MB。
结果为145 QPS,偶尔会出现1个失败请求。不得不说这个玩意太强了,纯Python写的服务器居然不输C/C++写的,只是内存占用有点大。加上它支持长连接,看上去是个不错的选择。

顺带一提,我稍后又测了下Eurasia 3.1:

import amysql
from random import randint

con = amysql.Connection()
con.connect ('127.0.0.1', 3306, 'root', '123', 'test')
con.query('SET GLOBAL query_cache_size = 0;')

from eurasia.web import httpserver, mainloop

def handler(httpfile):
    sum = 0
    for i in xrange(10):
        r = randint(1, 1000000 - 10)
        rs = con.query('SELECT value FROM test WHERE id >= %s LIMIT 10;', (r,))
        for j in rs.rows:
            sum += j[0]
    httpfile['Content-Type'] = 'text/plain'
    httpfile.write('%6f' % sum)
    httpfile.close()

httpd = httpserver(('', 8080), handler)
httpd.start()
mainloop()

结果为145 QPS,无失败请求。此时重测了一下Tornado,为149 QPS。
值得一提的是,引入eurasia.web必须在con.connect()之后,否则会连接失败(RuntimeError: createSocket API returned NULL (0)),这可能是它修改了socket的原因。
虽然它也和Tornado一样支持长连接,不过提供的httpserver并不适合直接开发,要封装出一个好用的接口也很麻烦。于是直接使用wsgiserver应该是更好的选择(测试只比httpserver低1 QPS),不过这也就意味着还需要一个WSGI框架,也会稍微影响一点性能;而Tornado本身就已经包含框架了,所以感觉Tornado是更好的选择。

接着试试PHP。
用spawn-fcgi来跑,前端挂了nginx,可能会影响一些性能。

<?php
$sum = 0;
$db = new mysqli('p:127.0.0.1', 'root', '123', 'test'); // p:是使用持久连接,因为没法像Python那样重用连接
for ($i = 0; $i < 10; ++$i) {
    $r = rand(1, 1000000 - 10);
    $result = $db->query("SELECT value FROM test WHERE id >= $r LIMIT 10;");
    while ($row = $result->fetch_row()) {
        $sum += $row[0];
    }
    $result->close();
}
printf('%6f', $sum);
?>

启动占用5.3MB内存,测试完占用5.3MB。
结果为130 QPS,无失败请求。表现并不算好,但内存控制确实强大。

2011年9月5日更新:
刚发现PHP版本还可以使用prepared statement进行优化,于是再次测试:

<?php
$sum = 0;
$db = new mysqli('p:127.0.0.1', 'root', '123', 'test');
$stmt = $db->prepare('SELECT value FROM test WHERE id >= ? LIMIT 10;');
for ($i = 0; $i < 10; ++$i) {
    $r = rand(1, 1000000 - 10);
    $stmt->bind_param('i', $r);
    $stmt->execute();
    $stmt->bind_result($value);
    while ($stmt->fetch()) {
        $sum += $value;
    }
}
$stmt->close();

printf('%6f', $sum);
?>

结果为155 QPS,无失败请求,性能一跃成为最好的了。
值得一提的是Python没提供这种API,所以有失公平。
而且这个例子比较特殊,10次查询都用相同语句,真实环境下一般不会出现这种情况。而如果每次循环都创建$stmt的话,会下降到115 QPS,因此不需要多次使用的话,最好还是用前面的方法。

公平起见,拿Tornado + nginx试了下,成绩为138 QPS,无失败请求,性能损失约为5%。也许关闭gzip会提高,但真实环境下一般都要开启。

最终测完时还剩10MB内存,75MB缓存,看来128MB的小VPS也很强劲了。不说150 QPS,就算缩小10倍,1天也能处理130万动态请求了。

而如果需要扩展的话,CPU是限制QPS的主要原因:
将其设为100%(2.3 GHz)后,Tornado + nginx增加到351 QPS,基本上成正比。
设为双CPU后,启动2个进程,并发数设为500,QPS也增加到502。
设为4 CPU后,启动4个进程,QPS只能跑到582,因为i5虽然可以通过超线程虚拟到4核,但毕竟不是真实的CPU,增长还是有限。
这样看来比较好的服务器(3 GHz,8核)应该能支持2600 QPS,不过这时的瓶颈应该是MySQL了,只能加内存用Redis之类的缓存了。

最后,如果采用InnoDB的话,我必须增加虚拟机的内存,最终的内存消耗大概在160MB左右,40MB缓存,Tornado性能下降到132 QPS。但Oracle的评测却说InnoDB比MyISAM快,原因未知。

# 来源:keakon的涂鸦馆


在微博上关注: 新浪, 腾讯   投稿

最新招聘

更多>>

相关 [python php web] 推荐:

Python 和 PHP 的 Web 服务器性能测试

- 非狐外传 - python.cn(jobs, news)
这几天一直在玩虚拟机,测了几种Python和PHP的Web服务器的性能,顺便记录下来. 宿主:MacBook Pro MC700. 操作系统:Mac OS X 10.6.8. CPU: 2.3GHz Intel Core i5(双核). 虚拟机:Virtual Box. 操作系统:Linux version 2.6.32-5-686 (Debian 2.6.32-35).

Python和PHP的Web服务器性能测试

- phus - keakon的涂鸦馆
这几天一直在玩虚拟机,测了几种Python和PHP的Web服务器的性能,顺便记录下来. 宿主:MacBook Pro MC700. 操作系统:Mac OS X 10.6.8. CPU: 2.3GHz Intel Core i5(双核). 虚拟机:Virtual Box. 操作系统:Linux version 2.6.32-5-686 (Debian 2.6.32-35).

我的PHP,Python和Ruby之路

- heely - robbin的自言自语
因为看到一篇讨论PHP,Python和Ruby的编程语言讨论贴,就说说我的PHP,Python和Ruby之路吧:. 我2000-2001年用PHP用了两年,那还是第一次互联网泡沫时期,到2001年后期,Servlet/JSP流行,然后我就发现:你说用PHP写的东西,都会被人鄙视. 当时我们其实也用Java了,只不过用Java写后端的消息队列.

PHP, Perl, Python, Ruby 語法對照表

- Hming - Tsung&#39;s Blog
一個簡單又實用的頁面, 此頁將 PHP, Perl, Python, Ruby 的語法 做成 對照表.. 詳細可見: Scripting Languages: PHP, Perl, Python, Ruby - Hyperpolyglot.

[转][转]浅谈php web安全

- - heiyeluren的Blog
来源: http://www.phpben.com/?post=79. 首先,笔记不是web安全的专家,所以这不是web安全方面专家级文章,而是学习笔记、细心总结文章,里面有些是我们phper不易发现或者说不重视的东西. 在大公司肯定有专门的web安全测试员,安全方面不是phper考虑的范围. 但是作为一个phper对于安全知识是:“ 知道有这么一回事,编程时自然有所注意”.

知名Python Web框架Django被墙

- Haisheng HU - Solidot
xuby 写道 "今天发现,知名Python Web框架Django的官方网站被墙. 目前尚不清楚是迎八一暂时屏蔽,还是永久屏蔽. " 北京联通ISP可以访问Https版.

简易的python web服务器用途

- Ruby - Erlang非业余研究
原创文章,转载请注明: 转载自Erlang非业余研究. 本文链接地址: 简易的python web服务器用途. 我们在工作中经常会需要看下报表,如tsung的统计报表或者lcov的覆盖情况,这些报表通常为了方便都会作成html格式的. 我们可以把这些html网页打包拉回去用浏览器慢慢看,但是每次都要打包,拉数据非常麻烦.

【外刊IT评论网】PHP 5.4 内置web服务器

- - 外刊IT评论网
PHP是一种脚本语言,它需要PHP解释器来分析运行PHP文件. 当把PHP做为CGI服务Web请求时,它需要被嵌入到某种Web服务器里,最常见的是集成到Apache或ISS里,这就是说,在使用PHP前,你需要安装Apache或ISS,并且正确的配置它们和PHP集成的参数. 虽然这种配置已经很规范,文档非常丰富,但我们还是经常在安装Apache和PHP集成时遇到问题,而且,有时候我们只想测试一个简单的PHP特征,不想就为此安装、启动Apache服务.

Go 和 Python Web 服务器性能对比

- Ken - python.cn(jobs, news)
我通常使用 Python 来构建 Web 应用. 一年前,在兴趣的驱使下,我开始学习 Go. 在此期间,我重写了一些原本由 C 开发的 CGI 应用,包括运行于 chroot 环境下的同 thttpd 服务器一起的应用. 我开始寻找可以开发易于 chroot、且内置 Web 服务器的独立 Web 应用的工具.