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);
- }
- ?>
#p#
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分钟后,服务器进入假死状态。
#p#
备注:
因为不太相信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的性能完全在可以接受的范围内。
原文链接:http://blog.splayer.org/index.php/2010/10/performance-penalty-in-mvc-web-deployment/
所有测试源代码和nginx/php-fpm配置文件下载:http://blog.splayer.org/wp-content/uploads/2010/10/FBSD-SandBox.zip
【编辑推荐】