第18章性能.docx
《第18章性能.docx》由会员分享,可在线阅读,更多相关《第18章性能.docx(25页珍藏版)》请在冰豆网上搜索。
第18章性能
很en_hi____g_____________________________________________________________________________________________________________________
第18章-性能
如果你希望你的网站能够吸引很多人,优化网站性能将是在开发阶段的一个主要要素。
令人安心的是symfony核心开发者总会非常关注性能问题。
通过加速开发带来好处的同时也会带来一些多余的开销,symfony核心开发者总是会认识到性能的需求。
因此,每一个类每一个方法都会仔细的分析并优化到尽可能的快速。
基本的开销,可以通过比较使用和不使用symfony来显示"hello,world"的时间来测量,这个开销很小。
因此,这个框架是可扩展的并能在压力测试下表现的很好。
最好的证据是,一些高访问量的网站(有百万活跃用户的并有大量消耗服务器资源的Ajax交互的)使用symfony并且非常满意它的性能。
在wiki上可以看一下这用symfony开发的网站列表(http:
//www.symfony-
不过,很显然高访问量的站点通常会扩展服务器数量并升级到他们想要的硬件。
如果你没有足够的资源做到这一点,或者如果你想确保框架的全部力量都在你的掌握中,你可以使用几个调整来进一步加快你的symfony应用程序。
本章列出了一些在框架所有层次中和更多高级用户的推荐优化性能方法。
它们中的一些在以前的章节中已经提过,但是你会觉得把它们都集中在一起会对你十分有帮助。
调整服务器
一个精心优化的应用程序应该放在一个优化良好的服务器上。
你应该了解服务器性能的基础知识,以确保symfony运行没有瓶颈。
这里有几样东西需要查核,以确保你的服务器不会过于缓慢。
在php.ini中设置magic_quotes_gpc为on会降低应用程序效率,因为这会让PHP把请求参数中的所有引用都转义,但symfony会在后来系统化的过程中还原它们,这样唯一的后果就是时间上的损失--并会在一些平台上带来引用-转义问题。
因此,如果能修改PHP配置的话,设置这个参数为off。
PHP版本越新越好。
PHP5.2比PHP5.1快,PHP5.1比PHP5.0快。
所以请升级你的PHP来获得最新的性能提升。
在生产服务器上使用PHP加速器(例如APC,XCache或者eAccelerator)几乎是必须的,因为它能让PHP跑的比平均快50%。
安装其中一个加速器扩展来感觉一下PHP的真实速度。
此外,在生产服务器上确认关闭了debug程序,例如Xdebug或者APD扩展。
NOTE你也许会担心mod_rewrite扩展的开销:
这其实是可以忽略的。
确实,通过重写规则来读取一张图片比不通过重写规则来读取慢,但是放慢的量级低于执行任何的php语句。
一些symfony开发者喜欢使用syck,这是一个YAML分析器,PHP的一个扩展,它可以替代symfony的内部分析器。
这确实比较快,但symfony的缓存系统已经让YAML分析的开销最小化了,所以使用syck不会给已有生产环境带来什么益处。
你小心syck不是很成熟,使用的时候也许会发生错误。
不管怎么说,如果你感兴趣,安装这个扩展(symfony会自动使用它的。
TIP当一台服务器不够用的时候,你可以增加其他服务器来负载均衡。
只要uploads/目录是共享的并且使用了数据库存储用户会话,symfony项目会无缝的嵌入负载均衡架构。
调整模型
在symfony中,模型层是公认最慢的部分。
如果通过基准程序测试发现需要优化模型层,这里有一些可能的改进方法。
优化Propel整合
初始化模型层(核心Porpel类)会花一些时间,因为它需要去载入一些类并构造多个对象。
无论如何,因为symfony整合了Propel,所以这些初始化任务只会在动作确实需要模型的时候才会发生,并且会尽量晚发生。
Propel类只会在当生成的模型对象自动载入的时候才会被初始化。
这就意味不使用模型的页面不会被模型层所累。
如果你的应用程序完全不需要使用模型层,你也能在settings.yml中设置关闭所有的层并保存在sfDatabaseManager的初始化值中:
all:
.settings:
use_database:
off
生成的模型类(在lib/model/om/)已经被优化过了——他们不包含注释,并且他们从自动载入机制中获益。
依靠自动载入替代手动包含文件意味着类会在确实需要的时候才会被载入。
因此在不需要模型类的情况下,有自动载入机制会节省执行时间,若使用include语法来实现则不会节省时间。
对于注释,他们注解了生成的方法,但是会使模型文件变大——结果会导致轻微的磁盘读取开销。
因为生成的方法名是非常清楚的,所以默认注解是关闭的。
这两个加强是针对symfony的,但你能通过修改propel.ini文件恢复默认值,如下:
propel.builder.addIncludes=true#在生成的类中增加include
#来替代自动载入机制
propel.builder.addComments=true#在生成的类中增加注释
限制化合(Hydrate)对象数量
当用peer类的方法来获得对象的时候,查询通过化合(hydrating)处理(基于查询结果的行来创建和填充对象)。
例如,通常可以使用下面语句通过Propel获得article表的所有行:
$articles=ArticlePeer:
:
doSelect(newCriteria());
$articles变量得到的值是Article类的对象数组。
每一个对象都被创建并初始化了,这需要一些时间。
从而得到一个结论:
相对于直接访问数据库的查询语句,Propel查询语句的速度直接取决于它返回结果的数量。
这就是说你的模型方法应该经过优化过只返回指定数量的结果。
当你不需要从Criteria获得所有结果的时候,你应该使用setLimit()和setOffset()方法来限制返回结果数量。
例如,如果你只需要获得第10-20行结果,可以如例18-1中一样改进一下Criteria。
例18-1-限制Criteria返回的结果数量
$c=newCriteria();
$c->setOffset(10);//第一个返回记录的偏移量
$c->setLimit(10);//返回记录数量
$articles=ArticlePeer:
:
doSelect($c);
这可以通过使用翻页来自动完成。
sfPropelPager对象通过自动处理offset和Propel查询语句的limit来获得对象的特定页的数据。
更多这个类的信息可以参考API文档。
用Join让结果数量最小化
在应用程序开发过程中,你应该关注每个请求会产生多少个数据库查询语句。
网页调试工具条显示了每页有多少条查询语句,点击数据库图标会显示出这些SQL查询语句。
如果看到查询语句的数量增加有异常,就要考虑一下使用join了。
在解释join方法之前,让我们回顾一下循环一个对象数组并用Propel获得相关类的资料时会发生什么,如例18-2所示。
这个例子假设你的设计(schema)描述了一个带有author表外键的article表。
例18-2-在循环中获得相关类的详细信息
//在动作中
$this->articles=ArticlePeer:
:
doSelect(newCriteria());
//通过doSelect()发出的数据库查询
SELECTarticle.id,article.title,article.author_id,...
FROMarticle
//在模板中
phpforeach($articlesas$article):
?
>
phpecho$article->getTitle()?
>,
writtenby
phpecho$article->getAuthor()->getName()?
>
phpendforeach;?
>
如果$articles数组包含了十个对象,那么当类Author的对象调用化合(hydrate)的时候会依次执行十次getAuthor()方法,如例18-3所示。
例18-3-外键获取方法发出了一个数据库查询
//在模板中
$article->getAuthor()
//getAuthor()发出的数据库查询
SELECTauthor.id,author.name,...
FROMauthor
WHEREauthor.id=?
//?
是article.author_id
所以例18-2中的翻页总共需执行11条查询语句:
其中一个当然是用来建立Article对象的,其余的10条查询语句是用来逐次建立Author对象。
仅仅显示文章和他们的作者列表却需用多条查询语句来完成。
如果只是使用简单的SQL语句,你应该知道如何减少查询语句,只用一条语句来获得article表和相关author表的内容。
这就是ArticlePeer类的doSelectJoinAuthor()方法做的事情。
它提供了比单纯doSelect()调用更复杂的查询语句,但在结果集中增加了列,设置允许Propel来融合Article对象和相关的Author对象。
例18-4中的代码展示了和例18-2同样的效果,但是只需要一条数据库查询语句而不是以前的11条语句来处理,这会处理的更快。
例18-4-在一条语句中获得文章详细资料和他们的作者
//在动作中
$this->articles=ArticlePeer:
:
doSelectJoinAuthor(newCriteria());
//doSelectJoinAuthor()发出的数据库查询
SELECTarticle.id,article.title,article.author_id,...
author.id,author.name,...
FROMarticle,author
WHEREarticle.author_id=author.id
//在模板中(没有改变)
phpforeach($articlesas$article):
?
>
phpecho$article->getTitle()?
>,
writtenby
phpecho$article->getAuthor()->getName()?
>
phpendforeach;?
>
调用doSelect()或doSelectJoinXXX()方法对返回的结果来说没有区别;他们都返回了同样的对象数组(如例中的Article类)。
在这之后使用这些对象的外键获取方法才会看出不同。
在用doSelect()的情况下,他发出了查询,一个对象会产生一个结果;而在用doSelectJoinXXX()的情况下,外部对象已经存在了,不需要用查询语句了,所以处理过程会快些。
因此,如果你知道需要用到相关的对象的话,调用doSelectJoinXXX()方法会减少数据库查询语句的数量——并提高了分页的效率。
doSelectJoinAuthor()方法是根据article和author表的关系在调用propel-build-model时自动产生的。
如果在article表结构中有其他的外键(例如,对于分类表)生成的BaseArticlePeer类就会有其他的Join方法,如例18-5所示。
例18-5-ArticlePeer类可用的doSelect方法示例
//获得Article对象
doSelect()
//获得Article对象和hydrate相关作者对象
doSelectJoinAuthor()
//获得Article对象和hydrate相关目录对象
doSelectJoinCategory()
//获得Article对象和hydrate相关作者和目录对象
doSelectJoinAuthorAndCategory()
//等价于
doSelectJoinAll()
Peer类也包含了doCount()的Join方法。
有i18n关联的类(见第13章),提供了doSelectWithI18n()方法,这个方法和Join方法很像不过它作用于i18n对象。
要在模型类中发现可用的Join方法,你应该检查lib/model/om/中生成的peer类。
如果你没找到查询所需要的Join方法的话(例如,没有自动生成多对多关系的Join方法),可以自行建立并扩展你的模型。
TIP当然,调用doSelectJoinXXX()会比调用doSelect()慢些,所以只有之后需要使用化合后(hydrated)的外键对象的时候才会提高整体性能。
避免使用临时数组
当时用Propel时,对象已经被化合(hydrated),所以不需要在模板中准备临时数组了。
开发者不习惯使用ORM通常导致:
尽管模板可以直接依靠现有对象的数组来实现,他们还是想要准备一个字符串或者数字数组。
例如,想象一下一个模板来显示从数据库中获得的所有文章主题列表的情况。
一个不使用OOP的开发者通常会写成如例18-6这样。
例18-6-已经有数组了,动作中再准备一个数组是没有用处的
//在动作中
$articles=ArticlePeer:
:
doSelect(newCriteria());
$titles=array();
foreach($articlesas$article)
{
$titles[]=$article->getTitle();
}
$this->titles=$titles;
//在模板中
这段代码的问题是hydrating已经在调用doSelect()时完成了(需要花些时间),建立$titles数组纯属多余,因为你能改写为例18-7所示的代码。
因此用来建立$titles数组的时间可以节省下来用来提高应用程序的效率。
例18-7-使用对象数组可以让你不用建立临时数组
//在动作中
$this->articles=ArticlePeer:
:
doSelect(newCriteria());
//在模板中
phpforeach($articlesas$article):
?
>
phpecho$article->getTitle()?
>
phpendforeach;?
>
如果因为一些对象的处理过程中确实需要用临时数组,正确的方法是在你的模型类中建立一个新的方法直接返回这些数组。
例如,如果需要一个文章主题数组和每个文章的评论数量的话,动作和模板应如例18-8这样。
例18-8-使用自定义的方法替代临时数组
//在动作中
$this->articles=ArticlePeer:
:
getArticleTitlesWithNbComments();
//在模板中
是否在模型中建立一个快速的处理过程getArticleTitlesWithNbComments()方法取决于你——例如,通过绕过整个对象关系映射和数据库抽象层来完成。
绕过ORM
当你确实不需要对象而只需要从一些表中获得一些字段的时候,如同以前的示例中,你能在模型中建立特殊的方法,直接通过Creole调用数据库完全绕过ORM层。
例如,返回一个自定义的数组。
例18-9说明了这个构想。
例18-9-在lib/model/ArticlePeer.php中直接Creole访问来优化模型方法
classArticlePeerextendsBaseArticlePeer
{
publicstaticfunctiongetArticleTitlesWithNbComments()
{
$connection=Propel:
:
getConnection();
$query='SELECT%sastitle,COUNT(%s)ASnbFROM%sLEFTJOIN%sON%s=%sGROUPBY%s';
$query=sprintf($query,
ArticlePeer:
:
TITLE,CommentPeer:
:
ID,
ArticlePeer:
:
TABLE_NAME,CommentPeer:
:
TABLE_NAME,
ArticlePeer:
:
ID,CommentPeer:
:
ARTICLE_ID,
ArticlePeer:
:
ID
);
$statement=$connection->prepareStatement($query);
$resultset=$statement->executeQuery();
$results=array();
while($resultset->next())
{
$results[]=array($resultset->getString('title'),$resultset->getInt('nb'));
}
return$results;
}
}
当你开始建立这些方法的时候,你最后可能会为每个动作写一个自定义方法,这会失去层分离带来的好处,而且还失去了数据库独立性。
TIP如果Propel作为模型层不适合你,在手写查询语句前考虑一下使用其他的ORM。
例如,如果想用PhpDoctrineORM的话,可以看一下sfDoctrine插件。
还有,你能用其他的数据库抽象层来代替Creole,从而直接访问数据库。
在PHP5.1里,PDO绑定在PHP中,而且比Creole快。
数据库加速
有许多针对数据库的优化技巧可以在使用symfony的时候用到。
本节简单地列出最常用的数据库优化策略,但是良好的理解数据库引擎和管理数据库对于使用模型层会有很好的帮助。
TIP记住网页调试工具条显示了每个页面执行查询语句的数量,应该监测每一个微调来确认是否增强了性能。
全表查询通常会发生在没有主键的列。
要加速这些查询语句,你应该在数据库设计(schema)中定义索引。
要增加一列索引,给列定义增加index:
true属性,如例18-10所示。
例18-10-在config/schema.yml增加一个单列索引
propel:
article:
id:
author_id:
title:
{type:
varchar(100),index:
true}
你也可以使用另一个选择:
用index:
unique语法定义一个唯一索引替代标准的索引。
你也可以在schema.yml中定义多列索引(关于索引语法可以参考第8章)。
强烈建议考虑这些方法,因为这通常会对一个复杂的查询有很大的帮助。
在schema中增加索引后,你还需要对数据库作同样的操作,可以在数据库中直接使用ADDINDEX语句或是调用propel-build-all命令行(这不只是重建表结构,也会清空所有已存在的数据)。
TIP索引会加速SELECT语句的查询效率,但会让INSERT,UPDATE和DELETE语句变慢。
数据库引擎在每个查询语句只使用一个索引并且基于内部启发式的方法来推断使用哪个索引。
增加索引有时会对效率带来不利的影响,所以确认你在监测效率是否有所提高。
除另有规定外,在symfony中每一个请求使用一个数据库连接,每一个连接在请求完成后会被关闭。
在databases.yml文件中设置presistent:
true,可以开启持久数据库连接,这样在不同的查询之间数据库连接池会一直保持开启,如例18-11所示。
例18-11-在config/databases.yml中激活永久数据库连接支持
prod:
propel:
class:
sfPropelDatabase
param:
persistent:
true
dsn:
mysql:
//login:
passwd@localhost/blog
这可能会增强数据库总体性能也可能不会,取决于很多因素。
在因特网上关于这个主题的文档很多。
请确定在修改选项前你测试过应用程序的性能来验证它的结果。
SIDEBAR针对MySQL的技巧
MySQL配置文件中的许多可以改变数据库性能的设置都放在f文件中。
确认读过关于此主题的在线文档(
MySql提供了一个工具,慢查询记录(slowquerieslog)。
所有SQL执行时间超过long_query_time设置(此设置可以在f中更改)的都会被记录在一个文件中,这很难手动统计,但是用一个mysqldumpslow命令可以方便地列出总结。
这是一个很棒的用来查找需要优化的查询语句的工具。
调整视图
按照不同的方法设计和实现视图层,可能会有一些小的速度减少或者提升。
这小节讲述的是替代品和它们的优缺点。
使用最快的代码片段
如果没有使用缓存系统,你要注意include_component()比include_partial()要慢一些,include_partial()比PHP的include也要慢一些。
这是因为symfony初始化了一个视图来包含一个局部模板和一个sfComponent类的对象来包含一个组件,包含这些文件会对总体性能带来一些小的影响。
不过,除非在模板中引用了许多局部模板或者组件,否则这对系统开销不是很大。
这也许会发生在每次在foreach中调用include_partial()辅助函数来做列表或者表格的时候。
当你注意到有大量的局部模板或者组件包含非常影响性能时,应该考虑使用缓存(见第12章),如果不想用缓存,那只能用简单的include替代了。
槽(slot)和组件槽(componentslot)之间的性能的差别是可以感觉得到的。
设置并包含一个槽(slot)的处理时间是可以忽略的——这等于初始化一个变量。
但是组件槽(componentslot)依靠一个视图配置,他们需要初始化一些对象才能工作。
不过,组件槽(componentslot)可以在调用模板时被单独缓存,与之相反槽(slot)总是在包含它的模板里被缓存的。
加速路由过程
正如第9章解释过的,在模板中每一次调用链接辅助函数都会请求路由系统来把内部URL转换为外部URL。
这是通过在routing.y