横向压力测试:Ruby on Rails PK CakePHP
Ruby on Rails以优雅的MVC架构闻名,这个架构如此诱人和美丽,而CakePHP则是PHP开发中常用的框架之一。如果你不想束缚于传统的PHP的砖头式开发,那么你可以尝试转向MVC架构,不过Rails的性能和部署问题一直让人担心。
两者对比的话题在网上众说纷纭,很少见到客观而有说服力的论证和充分模拟实际环境下的压力测评。作为架构选型的重要决定,我们既不能人云亦云,更不可凭空臆想,一定要有充分的测试数据才能帮助做出正确的决定。
心动不如行动,立刻着手安排了仿真环境测试。第一步是设计测试方案:
压力测试的目标集中在Ruby On Rails和CakePHP的效率,所以采用同样的Nginx生产环境,但避开所有数据库操作以避免瓶颈转嫁到数据库影响结果。
代码的主要部分都是通过输出128000个4位的十进制随机数,来模拟总计约500KB的页面数据输出。调用的指令都很基本,对脚本测试来说很公平。
不过既然是虚拟高压力测试,实际环境中数据库读写等操作的时间开销应该有一个仿真替代,所以通过Sleep 200ms来仿真具有高度数据压力的服务端。当然我们都知道Sleep是没有真实的cpu开销的,所以不会影响测试结果的公平。
测试工具使用经典的ApacheBench。先后测试10并发100请求(-c 10 -n 100) 的中等压力,和200并发5000请求(-c 200 -n 5000)高压测试。
环境
OS: FreeBSD 8.1 CPU: Intel 4核心 Core 2 RAM: 4GB 内存 PHP环境:nginx+php-fpm(5.3.3)+APC Rails环境:nginx+passenger+Ruby(1.8.7) on Rails(3.0.0) 所有软件均使用ports安装
fpm的优化配置:
pm.max_children = 1000 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 1000
passenger的优化配置(nginx.conf):
passenger_max_pool_size 300;//4GB内存最大的允许值,再追加便无法启动passenger
通过Rails脚本创建Test App:
rails new dummy
Ruby on Rails 代码:
// app/controller/test_controller.rb class TestController ApplicationController def index sleep(0.2) end end// app/views/test/index.html.rb % 128000.times do %>%=rand(8999)+1000%>% end %>
PHP代码:
// vsruby.php php usleep(200000); echo "html>head>head>body>"; for($i = 0; $i 128000;$i++) { echo mt_rand(8999,9999); } echo "body>html>";
CakePHP代码:
// CakePHP // app/controller/test_controller.php php class TestController extends AppController { var $name = 'Test'; function index() { usleep(200000); } } // CakePHP // app/views/test/index.ctp php for($i = 0; $i 128000;$i++) { echo mt_rand(8999,9999); } ?>
10并发100个请求:
// Ruby on Rails // CPU usage: 100% Server Software: nginx/0.8.52 Server Hostname: 127.0.0.1 Server Port: 80 Document Path: /test/ Document Length: 512731 bytes Concurrency Level: 10 Time taken for tests: 40.939 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 51334500 bytes HTML transferred: 51273100 bytes Requests per second: 2.44 [#/sec] (mean) Time per request: 4093.898 [ms] (mean) Time per request: 409.390 [ms] (mean, across all concurrent requests) Transfer rate: 1224.54 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.0 0 0 Processing: 1231 4036 3167.1 3149 16396 Waiting: 1203 2428 2533.7 1625 15683 Total: 1231 4036 3167.1 3150 16396 Percentage of the requests served within a certain time (ms) 50% 3150 66% 3353 75% 3679 80% 3893 90% 12307 95% 12307 98% 16108 99% 16396 100% 16396 (longest request)//php //CPU usage: 20-30% Server Software: nginx/0.8.52 Server Hostname: 127.0.0.1 Server Port: 80 Document Path: /php/ Document Length: 512039 bytes Concurrency Level: 10 Time taken for tests: 4.144 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 51218600 bytes HTML transferred: 51203900 bytes Requests per second: 24.13 [#/sec] (mean) Time per request: 414.389 [ms] (mean) Time per request: 41.439 [ms] (mean, across all concurrent requests) Transfer rate: 12070.36 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 0 Processing: 400 405 14.0 403 502 Waiting: 201 205 3.1 204 218 Total: 400 405 14.0 403 502 Percentage of the requests served within a certain time (ms) 50% 403 66% 404 75% 405 80% 405 90% 408 95% 409 98% 501 99% 502 100% 502 (longest request)// CakePHP Server Software: nginx/0.8.52 Server Hostname: 127.0.0.1 Server Port: 80 Document Path: /cakephp/ Document Length: 512652 bytes Concurrency Level: 10 Time taken for tests: 4.036 seconds Complete requests: 100 Failed requests: 0 Write errors: 0 Total transferred: 51291900 bytes HTML transferred: 51265200 bytes Requests per second: 24.78 [#/sec] (mean) Time per request: 403.553 [ms] (mean) Time per request: 40.355 [ms] (mean, across all concurrent requests) Transfer rate: 12412.20 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.7 0 6 Processing: 302 399 119.1 363 775 Waiting: 275 370 119.9 340 764 Total: 302 400 119.1 364 775 Percentage of the requests served within a certain time (ms) 50% 364 66% 372 75% 378 80% 381 90% 725 95% 755 98% 775 99% 775 100% 775 (longest request)
5000个请求,200并发数:
// php Server Software: nginx/0.8.52 Server Hostname: 127.0.0.1 Server Port: 80 Document Path: /php/ Document Length: 512039 bytes Concurrency Level: 200 Time taken for tests: 82.243 seconds Complete requests: 5000 Failed requests: 0 Write errors: 0 Total transferred: 2560930000 bytes HTML transferred: 2560195000 bytes Requests per second: 60.80 [#/sec] (mean) Time per request: 3289.722 [ms] (mean) Time per request: 16.449 [ms] (mean, across all concurrent requests) Transfer rate: 30408.75 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 1.6 0 20 Processing: 405 3258 4830.3 2675 56787 Waiting: 202 1048 1324.8 344 53432 Total: 405 3259 4830.3 2676 56787 Percentage of the requests served within a certain time (ms) 50% 2676 66% 3081 75% 3361 80% 3535 90% 3828 95% 4262 98% 5709 99% 31863 100% 56787 (longest request) // CakePHP Server Software: nginx/0.8.52 Server Hostname: 127.0.0.1 Server Port: 80 Document Path: /cakephp/ Document Length: 512652 bytes Concurrency Level: 200 Time taken for tests: 99.652 seconds Complete requests: 5000 Failed requests: 0 Write errors: 0 Total transferred: 2565102923 bytes HTML transferred: 2563767656 bytes Requests per second: 50.17 [#/sec] (mean) Time per request: 3986.073 [ms] (mean) Time per request: 19.930 [ms] (mean, across all concurrent requests) Transfer rate: 25137.36 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 4 57.8 0 1663 Processing: 367 3969 1825.7 3857 10630 Waiting: 280 1543 731.9 1297 3953 Total: 472 3973 1824.8 3860 10630 Percentage of the requests served within a certain time (ms) 50% 3860 66% 4466 75% 5065 80% 5426 90% 6482 95% 7337 98% 8599 99% 8847 100% 10630 (longest request) // Rails //约10分钟后,服务器进入假死状态。
备注:
因为不太相信ruby的性能会有这样大的差距,怀疑是否ruby的rand()效率格外的低造成问题,我在测试完成又将rand()去掉,改为直接输出数字,但脚本执行时间并没有明显缩短。所以应该说是 ruby 对循环或数据输出的处理效率不佳导致。
结论
坦白说,几个ab测试跑下来,ruby的成绩如此之差,不但10并发的100请求就已经用满了服务器资源,更甚至没有能通过200并发5000请求的高压测试,这把我自己也吓了一跳。想到坚持在使用Rails的twitter,心中的敬佩油然而生。不知道那是什么样的硬件或软件优化,才可以用Ruby来支撑那样巨大的访问量。
客观的从纯性能的角度出发,在生产环境中,Ruby/Rails还是只适合Small Business。对与压力较高的服务或应用,就必须投入大量额外的硬件资源才能维持。本次测试中,Ruby On Rails与CakePHP的性能差距达到10倍之多,实在让我不敢考虑把Rails用在生产环境中。另一方面,PHP依托庞大的社区,多年来积累了众多的优化手段,其性能领先也有它的道理。在PHP架构之上的MVC候选人CakePHP,性能虽然相对于传统php的代码书写方法略有损失,但这个损失不到10%。所以对于在考虑MVC架构的php用户来说,CakePHP的性能完全在可以接受的范围内。