现在的位置: 首页 > 综合 > 正文

优化Zend Framework应用(转)

2013年08月04日 ⁄ 综合 ⁄ 共 6817字 ⁄ 字号 评论关闭
文章目录

 

2008-11-08 15:56

来到IDG之后,我们当前的工作就是把所有应用从PHP4代码基转移到PHP5代码基(每个人都应该这么做),这个过程中,我们还把所有的应用移植到Zend Framework中。在第一个项目改造完成,进入性能测试阶段时,我们遇到了大量的问题。我在这里列举了一些你可以避免的典型错误,请容忍我的唠叨。

几天前,我找到了一篇Till的文章,该文列举了许多优化ZF应用的陷阱。尽管我们的项目不像他那个项目那样,拥有那么多的访问量,但是我们也没有那么多的硬件资源。我再强调一次,我们的硬件资源要比他的项目少很多。

对于我们的应用,我们的机器配置为:双核2G处理器、2G内存和一些scsi硬盘。而且2G内存要被另外一个apache 1.3 的实例用掉1.4G,所以我们最后可以用于apache2和PHP5的内存只有600M。别的就不能告诉你了 :)

 

这篇文章分为以下几个主题展开:

  • 计算你的需求
  • 测试应用的实际性能
  • 通用的优化方法
  • Apd和PHP配置
  • Zend Framework建议

计算你的需求

在优化你的应用之前,你应该计算一下应用需要达到多大的吞吐量,比如说,我们的应用需要支持每个月300万的页面访问量,25万独立访问者,然后才开始对峰值性能进行优化。在这个例子中,我们把页面访问量除以28(一个月至少有28天,即当闰年的二月),得到每天是10.8万的访问量,然后再除以24、60和60,最后得到这个应用最少需要这个吞吐量:

3.000.000 / 28 / 24 / 60 / 60 = 1.24 请求/秒

同样的计算可以应用于并发用户:

250.000 / 28 / 24 / 60 / 60 = 0,1 并发用户

当然,这不是实际的最高值,我们需要让应用达到比当前统计更高的性能,让它可以承受更大的流量。现在的首要任务仍然是获取需要达到的最低流量,在访问高峰期(可以查看站点的统计报告得知),访问量必然要高于其他时间,找出这个时间段内的访问量。在我们的项目中,这个时间段是11:00 AM到22:00 PM,所以用11小时代替原来公司中的24小时:

3.000.000 / 28 / 11 / 60 / 60 = 2.7 请求/秒
250.000 / 28 / 11 / 60 / 60 = 0,2 并发用户

这才是你实际要达到的性能要求。因为我们想提供更高的吞吐量,所以我们把目标定的更高,我们的要求是:10 请求/秒和100并发用户。

测试应用的实际性能

如你在Till文章中看到的那样,你可以方便地在命令行中使用ApacheBench(ab)对应用进行性能测试,并且要保证是对应用本身的测试,不要有中间层或开启缓存功能。最好的办法是在本地的机器上测试,不过有时候很难有这个条件。@mathieuk推荐使用Siege,它可以实现更深入的测试(如多url测试等),或许这个工具能够提供很多帮助。在本文中,我仍然使用ApacheBench,不过以后我肯定会去试用一下Siege这个工具。

对性能测试,我们进行一个简单的测试,获取一个基准结果。即100个并发用户,对首页进行1000次访问:

BASH:

  1. ab -n 1000 -c 100 http://www.example.com/
    (不要忘记url末尾的斜杠)

你可以从这个测试中得到很多信息,并确保每次优化代码之后,都运行相同的测试用例,这样可以确定是否已经达到自己的优化目标,事实上,还可以对多次测试的统计数据进行比较,检查优化的效果。你要的数据可能像下面的例子:

  1. 每秒请求数: 4413.30 [#/sec] (平均值)
    或其他你要的数据。

通用的优化方法

好,现在开始切入正题。但在此之前,需要你为自己的Web应用做几件简单的事情:

确保已经设置AllowOverride None

请在虚拟主机配置或httpd配置中设置ZF的重写规则,否则每次请求都需要解析.htaccess文件。重写规则不应该经常变化,没有必要在每个应用中存在.htaccess文件。(或者通过svn配置虚拟主机和部署应用,这么做非常方便)

确保通过CDN为所有图片提供服务

为静态文件设置一个DNS别名是最简单的办法。把图片和CSS等静态文件放置在静态主机上,你可以配置主机名类似为server[1-4].static.*的主机,在同一个服务器上提供这些静态文件服务。客户端浏览器将会非常喜欢这种部署方式(然而如Yahoo所言,不要多于四台CDN服务器)。
可以使用轻量级的Web服务器为静态文件提供服务,如lighttpd、nginx或其它类似服务器,或者配置一个精简版的apache服务器也是可行的。
不要忘记添加缓存和gzip压缩支持。在linux/apache服务器上,我们对静态域使用以下的规则:

APACHE:

  1. # Also see: http://developer.yahoo.com/performance/rules.html#gzip
  2. AddOutputFilterByType DEFLATE text/css application/x-javascript application/javascript
  3. # Use expires headers for images
  4. # Also see: http://developer.yahoo.com/performance/rules.html#expires
  5. ExpiresActive On
  6. ExpiresByType image/gif A604800
  7. ExpiresByType image/jpeg A604800
  8. ExpiresByType image/png A604800
  9. ExpiresDefault A604800
  10. # ETags are bad!
  11. # Also see: http://developer.yahoo.com/performance/rules.html#etags
  12. FileETag None

 

安装Yslow for firebug

Yahoo提供很多有趣的建议来教你如何优化客户端的代码,这比后端优化要简单很多。:)

安装 APC (或类似模块)

这个模块真的很有用。

 

切入正题

解决上面这些问题之后,现在开始真正进入ZF优化的讨论。我将使用PHP的apd模块,通过pprofp来测试我们应用的性能。

从APD文档中可知,需要在开始分析的代码行前添加apd_set_pprofp_trace()调用。在我们的ZF应用中,我们在index.php的第一行就调用该函数。

此后,每次执行这个页面,系统都会把我们需要的数据以一个pprofp跟踪文件的形式保存在硬盘上。通过该文件,可以查看页面的执行情况,这对于页面而言非常有用。你可以以选项-R来调用pprofp,然后得到下面类似的结果:

TEXT:

  1. Trace for /webdocs/com.example.www/releases/20081102125639/public/index.php
  2. Total Elapsed Time = 0.99
  3. Total System Time = 0.19
  4. Total User Time = 0.60
  5. Real User System secs/ cumm
  6. %Time (excl/cumm) (excl/cumm) (excl/cumm) Calls call s/call Memory Usage Name
  7. --------------------------------------------------------------------------------------
  8. 140.9 0.00 1.39 0.00 0.79 0.00 0.25 28 0.0000 0.0496 0 include
  9. 100.0 0.00 0.99 0.00 0.60 0.00 0.19 1 0.0000 0.9857 0 main
  10. 94.0 0.00 0.93 0.00 0.56 0.00 0.17 1 0.0000 0.9267 0 Zend_Controller_Front->dispatch
  11. 92.7 0.00 0.91 0.00 0.56 0.00 0.16 1 0.0000 0.9133 0 Zend_Controller_Dispatcher_Standard->dispatch
  12. 87.8 0.00 0.87 0.00 0.53 0.00 0.15 1 0.0000 0.8658 0 PageController->dispatch
  13. 81.3 0.00 0.80 0.00 0.49 0.00 0.13 6 0.0000 0.1335 0 Smarty->fetch
  14. 80.3 0.00 0.79 0.00 0.48 0.00 0.13 1 0.0000 0.7918 0 Zend_Controller_Action_HelperBroker->notifyPostDispatch
  15. 80.3 0.00 0.79 0.00 0.48 0.00 0.13 1 0.0000 0.7916 0 Zend_Controller_Action_Helper_ViewRenderer->postDispatch
  16. 80.3 0.00 0.79 0.00 0.48 0.00 0.13 1 0.0000 0.7913 0 Zend_Controller_Action_Helper_ViewRenderer->render
  17. 80.0 0.00 0.79 0.00 0.48 0.00 0.13 1 0.0000 0.7890 0 Zend_Controller_Action_Helper_ViewRenderer->renderScript
  18. 80.0 0.00 0.79 0.00 0.48 0.00 0.13 1 0.0000 0.7887 0 IDG_View_Smarty->render
  19. 80.0 0.00 0.79 0.00 0.48 0.00 0.13 1 0.0001 0.7885 0 IDG_View_Smarty->_run
  20. 66.4 0.00 0.65 0.00 0.37 0.00 0.12 21 0.0000 0.0312 0 Smarty->_smarty_include
  21. 53.6 0.00 0.53 0.00 0.28 0.00 0.07 186 0.0000 0.0028 0 IDG_Db_Adapter_Pdo_Mysql->query
  22. 27.5 0.07 0.27 0.02 0.11 0.04 0.16 125 0.0005 0.0022 0 require_once
  23. 12.3 0.00 0.12 0.00 0.09 0.00 0.01 22 0.0000 0.0055 0 ProductProperty->__construct
  24. 11.6 0.00 0.11 0.00 0.08 0.00 0.01 29 0.0000 0.0039 0 IDG_Db_Adapter_Pdo_Mysql->describeTable
  25. 11.3 0.00 0.11 0.00 0.07 0.00 0.02 11 0.0000 0.0101 0 Game->getProperty
  26. 10.7 0.00 0.11 0.00 0.02 0.00 0.01 93 0.0000 0.0011 0 Zend_Db_Statement_Pdo->execute
  27. 10.0 0.00 0.10 0.00 0.07 0.00 0.02 93 0.0000 0.0011 0 IDG_Db_Adapter_Pdo_Mysql->prepare

 

通过这个图表,你可以知道哪些方法被频繁调用,哪里可以做一些优化。(你可以通过-n选项查看更多的信息)。另外你可以得到完整应用树的一个完整列表,可以容易的发现哪些代码重复次数最多,这样就可以确定立即优化那些地方。

我们第一次运行pprofp,我们的列表树有229602行。这意味着每次对首页的请求,PHP引擎需要分析和读入229602行代码。几次优化之后,行数降到了36810行,这就好多了,但这花了很多的人力。在分析了这些工作之后,我们整理出以下一些需要牢记的规则。

>>>>由于字数限制,本文剩余内容在   优化Zend Framework应用(二)

 

-------------------------------------------------------------------------------------------

2008-11-08 16:04

Zend Framework 建议

仔细检查你的启动(bootstrap)文件

确定自己是否真的需要那么多的包含路径(include path),并确保使用频繁的路径在最前面,而不是最后(译注:PHP在找不到文件的时候,会逐个检查包含路径下是否存在该文件)。小心使用插件,不要启用项目不需要的插件。

Zend_Loader 要比 require_once快一点点

虽然只快一点点,但是包含文件很多的话,就会变得很重要。如Till所言,为项目编写一个专用的xyz_Loader用于包含文件,或许也是一个很好的做法,不过这么做的收益是因项目而异的。

Zend_Db

一段很优雅的代码,执行效率却可能很差。请尽量避免大数据量的查询,特别是多表连接。我们曾经遇到的一个例子是:查询结果有120多行记录,而查询的目的仅仅是获取一些字段的加和。
如果你不需要强大的prepare和execute功能,你可以像下面这样获取一个简单的查询值(当然这么做也有缺点):

PHP:

  1. $db = Zend_Registry::get('dbAdapter');
  2. $result = $db->getConnection()->query('SELECT COUNT(id) AS amount FROM articles');
  3. $row = $result->fetchObject();
  4. return $row->amount;

这么做,你可以去掉大量对你毫无用处的冗余调用。(并且可以很方便地通过Memcache缓存这个结果)。

Zend_Controller_Router_Route

尽量使用Zend_Controller_Router_Route,慎用Zend_Controller_Router_Route_Regex,你早已经知道这么做了,不是吗? :)

Zend_Filter

我们有一个UBB过滤器,用于解析用户评论中的UBB标签(如[url][/url]等)。我们自己编写了这个过滤器,但是一定要确保它尽可能的高效。如PHP文档所言,你应该尽可能的使用str_replace,请确保你一直是这么做的。

Zend_Config

我们的每一个应用有一个ini配置文件,在部署的时候进行配置。在应用中,我们通过如下的方式使用其中的变量值:

PHP:

  1. $config = Zend_Registry:get('config');
  2. $title = $config->application->title;

这么做非常漂亮,但是每次调用$config->application->title,都需要调用array_key_exists函数,它的调用过程如下所示:

TEXT:

  1. Zend_Registry::getInstance
  2. Zend_Registry::get
  3. Zend_Registry::getInstance
  4. Zend_Registry->offsetExists
  5. array_key_exists
  6. Zend_Registry->offsetGet
  7. Zend_Config_Ini->__get
  8. Zend_Config_Ini->get
  9. array_key_exists
  10. Zend_Config->__get
  11. Zend_Config->get
  12. array_key_exists
  13. Zend_Config->__isset
  14. Zend_Config_Ini->__get
  15. Zend_Config_Ini->get
  16. array_key_exists
  17. Zend_Config->__get
  18. Zend_Config->get
  19. array_key_exists

这么做效率很低吧?:) 因此,如果你需要经常使用其中的一个变量的话,请把它定义为一个常量(事实上也确实如此),就像这样:

PHP:

  1. if(!defined("APPLICATION_TITLE"))
  2. {
  3. define("APPLICATION_TITLE", $config->application->title);
  4. }

 

然后你就可以一直使用这个常量来代替原来的变量。

其他建议

其他一些在优化代码时需要考虑的重要问题:

避免使用shell_exec

千真万确,这个调用非常慢(在我们的应用中一次调用最慢时可以达到77毫秒)。如果你在程序中多次调用,会极大的拖累应用的执行效率。举个简单的例子:

BASH:

  1. file -i /tmp/file

它在每次调用都会占用大量资源,请尽可能的使用其它替代方案。

优化Smarty

不要加载你不需要的模块!

当然,优化代码的方法还有很多。这篇文章只是告诉你如何审视你的ZF代码,以及一些常见的陷阱。每个项目各有特色,优化的效果因项目而异。如果你有更好的建议,请在评论中告诉我。

翻译文章,仅供学习之用,版权归原作者所有。
原文网址:http://www.svdgraaf.nl/2008/11/07/optimizing-your-zf-web-application/

抱歉!评论已关闭.