深刻浅出Symfony2 - 如何提升网站响应速度

简介

Symfony2是一个基于PHP语言的Web开发框架,有着开发速度快、性能高等特色。但Symfony2的学习曲线也比较陡峭,没有经验的初学者每每须要一些练习才能掌握其特性。相对其余框架,Symfony2比较吸引人的特色有: php

  1. 支持DI(Dependency Injection,依赖注入)和IoC(Inversion of control)。
  2. 高性能。
  3. 扩展性强。
  4. 文档成熟、拥有成熟的社区支持。

本文经过对一个基于Symfony2框架所开发的网站页面进行逐步优化,最终实现页面加载速度的提升的例子,向读者介绍Symfony2框架的一些 核心功能和特色。经过阅读本文,你能够经过一些具体的例子了解Symfony2框架的优秀特性和技术特色,从而体会到使用Symfony2框架能够为网站 开发带来的各类优点。 css

适合人群

  • 本文适用于但愿提升PHP语言的开发技术,或者对Symfony2框架有兴趣的读者。
  • 本文也适用于系统架构师和各种技术决策者。

1.Symfony2的运行环境的设置

在我所演示的项目中,已经包含了一个页面,经过输入这个地址来打开它:http://your.host.com/appdev.php/testpage_1。出现的页面以下图所示: 前端

咱们打开浏览器自带的调试功能,而后刷新页面: java

能够看到,该页面充斥着大量的js/css/图片文件,而整个页面的加载速度居然达到了9.6秒。 编程

而若是打开这个页面:http://your.host.com/app.php/testpage1,出现的页面以下图所示: 后端

咱们发现页面的加载速度变成了4秒,同时众多js和css文件被各自合并成为了两个单独的文件(图中红框的部分)。 浏览器

形成上面两个页面打开速度大相径庭的缘由在于:若是经过不一样的入口文件(app.php和appdev.php)进入页面,Symfony2会根据入口文件的不一样,切换到不一样的运行环境。好比在默认配置中:经过app.php访问的页面,就是生产环境,而经过appdev.php访问的页面,则是开发环境。 Symfony2根据运行环境的不一样,运行程序时的配置也会不一样。好比细心的读者可能会发现,开发环境中页面的下方多了一条像是工具栏同样的东西(这是 Symfony2特有的开发调试栏)。环境的不一样会影响Symfony2程序运行的各个环节,如下列举了一些比较重要的不一样配置下的差别处:

功能 开发环境 生产环境
----- ----- -----
开发调试栏 会出现 不会出现
日志记录 记录详细的程序执行信息 只在程序出现错误的时候记录
css/js合并 不会

因此能够看出,css/js文件合并实际上是Symfony2自动根据环境不一样所开启或关闭的一个自带功能罢了,这个功能在Symfony2中叫作Assets管理,固然咱们也能够经过控制入口文件来实现开启或者关闭其余更多的功能。

经过Symfony2的环境配置功能开启或关闭各类自带功能就像在文本里改一个参数那么简单,而每一个不一样的环境又有一套独立的环境配置。 Symfony2提供了大量的参数供用户方便的配置各类功能,经过对不一样环境下的各个功能进行配置,能够很方便的设置出一套适合你本身的工做/生产环境。

接下来让咱们看看Assets管理模块还能为咱们作什么。

2.深刻Assets管理

经过对上述页面的分析,咱们发现虽然js和css文件合并了,但各自的文件内容却没有通过压缩,两个文件的大小分别是437k和310k,这显然是一个不太合理的数字。但咱们能够经过简单的配置,让Assets管理模块帮咱们在合并文件的同时对内容也进行压缩。

例如咱们选择使用uglifyjs2对js进行压缩,用yuicompressor对css进行压缩。在这些软件已经安装完毕的状况下,只须要修改app/config.yml的如下几行:

assetic:
    debug:          "%kernel.debug%"
    use_controller: false
    bundles:        ['ScourgenHFS2Demo1Bundle']
    java: /usr/bin/java
    filters:
        cssrewrite: ~
        uglifyjs2:
            compress: true
            mangle: true
            bin: /opt/local/bin/uglifyjs
        yui_css:
            jar: /usr/share/yuicompressor-2.4.7.jar

而后在layout模板中引入js/css的地方分别增长一个过滤器

'@ScourgenHFS2Demo1Bundle/Resources/public/css/public_home.css'
'@ScourgenHFS2Demo1Bundle/Resources/public/css/inner_city_line.css'
filter='?yui_css'
%}
<link rel="stylesheet" type="text/css" media="screen" href="{{ asset_url }}" />

...

'@ScourgenHFS2Demo1Bundle/Resources/public/js/common/title.js'
filter='?uglifyjs2'
%}

咱们再执行一下生成Assets的命令:

% php app/console assetic:dump  --env=prod                                                                                                                                  
Dumping all prod assets.
Debug mode is off.
03:14:06 [file+] /Users/scourgen/Desktop/InfoQ/
optimize_performance_of_pages_with_symfony2/HeadFirstSymfony2-Demo1/app/../web/css/2ff013f.css
03:14:14 [file+] /Users/scourgen/Desktop/InfoQ/

而后再打开刚才生产环境下的页面,这时会发现刚才的两个css和js文件的大小已经变成了271k和232k,文件内容也已经都变成了通过 uglifyjs2和yuicompressor压缩以后的内容。虽然两个文件大小依然很大,但若是考虑到它们在通过gzip压缩后的文件大小只有86k 和39k,也应该算是在合理范围以内了。

固然在实际开发中,咱们常常会碰到虽然服务端的js/css文件内容修改了,但客户端却保留着旧版本的缓存,致使页面样式和js功能出现问题的状况,而为了解决这个问题,一样能够经过修改配置实现:

#app/config.yml
#将framework的templating改为以下的样子:
…
templating:
    assets_version: 1
    assets_version_format: %%s?%%s
    engines:
        - twig
    assets_base_urls:
        http:
            - http://server1.dev
            - http://server2.dev
...

而后为css合并文件指定一个文件名:

'@ScourgenHFS2Demo1Bundle/Resources/public/css/public_home.css'
'@ScourgenHFS2Demo1Bundle/Resources/public/css/inner_city_line.css'
filter='?yui_css'
output='css/a.css'
%}

咱们再刷新一下页面,看看发生了什么。

这时刚才两个js和css的URL分别变成了:

  • http://server1.dev/css/a.css?1
  • http://server2.dev/js/9ad140b.js?1

虽然js和css文件的url后面都带上了一个变量(也就是上面所定义的assets_version),而因为URL的不一样,客户端将会从新下载这两个文件以免从缓存中读取旧的版本。但为何这两个文件的地址会变成serverx.dev呢?

其实这是Assets管理模块的另一个功能:将它所管理的文件路径变成绝对地址(也就是增长了上面配置文件中的 http://server1.dev和http://server2.dev两个域名)。并且在配置了多个域名的状况下,哪一个文件名匹配哪一个域名是固定 的,不会随机显示形成带宽浪费,而这实际上是由它的一套算法实现的。

经过这样的修改,咱们也获得了两个益处:

  1. 当页面的js/css/图片文件不少时,因为这些URL的域名都不同,可让浏览器在同一时间并发下载更多的文件,从而加快页面打开的时间(参考:浏览器并发链接数)。
  2. 因为这些URL的域名和网页所在的域名不同,因此HTTP头里不会携带cookie信息,可以减小网络带宽,从而实现cookie-free domain

相似的状况也有不少,在开发中为了实现最佳实践咱们每每须要绞尽脑汁,但若是使用Symfony2做为开发框架则会使问题变得很是简单,甚至简单到根本不用写代码,只须要更改几行配置就能实现。

话说回来,在通过这些调整以后,前端的载入速度看起来已经不错了。那么Symfony2是否也能够很方便的调试和优化后端代码?答案是确定的。接下来咱们看一下如何使用Symfony2调试和优化程序的性能。

3.调试和开发工具介绍

调试和优化程序的最基本前提就是:你得知道你的程序在干什么。这句话虽说得轻巧,但在许多框架面前却很难作到。这些框架在各类高新技术的封装下, 代码和程序逻辑也变得十分复杂和臃肿,想要得知你所使用的框架背后到底作了哪些具体的事情、或者想获取程序运行的堆栈和调用信息、以及 MySQL/NoSQL/MessageQueue……这些服务的调用和执行状况等等,都是不太容易的。而在没有这些信息支持的状况下,想去对后端程序作 调试和优化几乎是不可能的。

而获取这些信息以便进行开发和优化对于Symfony2来讲却很是容易,缘由有三个:

  1. Symfony2有一个调试工具栏,可以在每个页面打开时,在页面的最下方显示该页的程序调试信息,甚至可以从中直接找出致使页面响应速度缓慢的瓶颈。Symfony2自带的调试工具栏很是强大,下面选一个比较酷的功能给你们演示一下:

上图是一个显示程序执行顺序以及耗时的界面,经过这个界面,开发者能够很直观的看到程序的详细执行流程和顺序。经过这个界面,也能够看到每一个步骤所 占用的时间,从而发现影响程序执行速度的瓶颈,从而有针对性的进行优化。好比上图所示的程序执行流程里,一眼就能看出一共有三个地方执行的时间比较久(青 色的条比较长)。

  1. 高效且合理的框架设计使得Symfony2框架内的每一个模块都不互相耦合, 每一个模块都有本身的职责,能够单独为其执行测试用例而不依托其余模块。每一个模块也都会像一个独立的软件同样有其本身的版本发布周期,有的模块甚至拥有独立 的维护团队。这样的设计让Symfony2取得了复杂性和扩展性之间的平衡,也完成了架构上的解耦,从而直接在架构层面下降了整个框架的复杂度。因此对于 开发者来讲,不管是开发新功能,仍是优化现有程序,都会以为很是方便和高效。

  2. 日志记录功能把框架在执行时的全过程都完彻底全记录在了日志中,你惟一须要担忧的就是及时清理日志以避免磁盘空间占用过大。下图是日志的一部分,能够看到日志所记录的信息是很是详细的,让人不由联想起了一些重量级Java框架的日志的输出。

因此综上三点所述,Symfony2对于调试和优化是很是友好的,利用其自带功能和设计能够很方便的进行调试和优化。

Symfony2自己已经作得很是出色了,那么在Symfony2以外呢?

虽然你们在国内可能不太听获得Symfony2这个名字,但在国际市场上Symfony2但是很是出名的。许多IDE软件开发商都支持 Symfony2(比较出名的有PHPStorm、NetBeans和Eclipse with PDT),经过这些软件对于Symfony2的支持,可以让开发者更加方便快捷的进行开发工做。

而对于开源软件开发者,以及一些大网站的技术团队而言,他们也围绕Symfony2框架作了不少工做,也开源了许多他们本身的模块。在 Symfony2的托管平台Github上,Symfony2项目的fork量和star量分别为6428和2174(截止2013年4月中旬),位居所 有PHP项目的前列。而在最近发布的对2.2版本的开发统计显示,共有2035次提交以及711次申请合并,平均天天11次提交和4次申请合并。如此频繁 的代码变动速度也能证实Symfony2以及相关社区的活跃度。

有如此强大的开发工具和社区支持,无论开发者在碰到什么问题时,基本上均可以迎刃而解。固然就算碰到了比较复杂的难题,也能够在Symfony2的社区或IRC频道中询问其余开发者。固然也能够来找我,个人联系方式在本文最下面。

4. 如何使用缓存

对于优化程序性能来讲,通常会有三个方向:

  1. 优化代码语法
  2. 优化业务逻辑
  3. 优化框架及系统

代码语法的优化并不是本文的主题,而业务逻辑则又是由具体的网站功能所决定的,并不会有什么放之四海皆准的办法,因此本节主要介绍的是如何更加高效的使用Symfony2框架所附带的功能来提升网站的响应速度。

下面介绍一下Symfony2中最重要的优化功能之一 - 页面缓存功能。

对于一个页面来讲,常常会有多个程序块来负责分别处理不一样的页面部分,好比上文咱们所展现的这个页面中,可能会有一块程序来处理页面上方的黑色导航 条:显示全部的公交信息及判断用户所在地点;一块程序来处理页面的中间部分:根据用户是否登陆显示不一样的内容;一块程序来处理页面下方的线路详情信息;而 这个页面的最终内容其实就是页面样式模板加上这三个程序输出后的结果。若是但愿加快页面速度,最好的办法就是加快这三个程序的输出结果,甚至将结果缓存起 来以便从此直接调用。

那咱们看看如何对页面进行缓存,下面我将经过一个例子来向读者展现如何作到这点。

使用页面片断缓存,咱们先要在包含其余页面片断的代码上增长一个standalone参数:

{% render url('layout_top', {}) with {}, {'standalone': true} %}

而后在这个处理页面片断的方法上配置缓存信息。

/**
 * @Route("/esi/getTop", name="layout_top")
 * @Cache(public=true,expires="+1 hour", smaxage=3600, maxage=3600)
 */

能够看到经过配置,咱们为这个页面片断定义了一小时的过时时间,定义缓存的参数和HTTP头信息中控制缓存的参数是相同的(public,expires,maxage等)。

细心的读者会发现两个问题:

  1. 缓存控制的参数为何是写在注释里的?
  2. 为何这些参数和HTTP头信息中负责控制缓存的参数相同?这是一个巧合仍是有意为之?

其实经过回答这两个问题,读者能够理解Symfony2框架的另外两个重要功能:

  1. 支持使用注释(Annotation)来对程序进行配置,改变其实现逻辑。
  2. 使用ESI规范做为页面缓存的标准,缓存参数兼容HTTP头信息中的缓存参数。

Annotation这个单词是“注释”的意思,在编程开发领域特指一种编程语言可以经过注释来改变程序的运行逻辑。对于熟悉其余语言的读者来 说,Annotation其实并不陌生,好比在Java里就有各类Annotation,@Override和@GuardedBy等你们也都比较熟悉。 Annotation对于开发者来讲可以大大的简化程序的复杂度:把复杂的程序逻辑抽象成为参数配置。可是对于PHP来讲,PHP语言实际上是不支持 Annotation这个功能的,因而Symfony2在其框架内部实现了Annotation:第一次执行程序时,Symfony2会自动分析处理源文 件,并将结果缓存在文件系统中,下次程序再被执行时,Symfony2会自动执行上次生成的文件,从而避免每次都对源文件的注释进行分析,而整个过程对开 发者来讲是彻底透明的(这也能解释为何有些页面第一次打开会比较慢,但之后就会很快)。

Symfony2框架中大量使用了Annotation:从缓存的定义到路由的配置,甚至到表结构的定义,到处都使用了Annotation功能。 你甚至能够根据规范编写本身的Annotation。因此在使用Symfony2开发程序时,复杂的逻辑会变成一行行清晰的注释,程序的流程控制将变得非 常简单。

而对于页面缓存功能来讲,Symfony2有一个模块实现了兼容ESI协议的反向代理功能,从而容许开发者使用HTTP协议来控制页面缓存以及设置 过时时间等,因此在网站规模变大的时候,开发者也能够平滑地将自带的反向代理模块升级成专门的反向代理服务(例如使用Varnish),从而提高网站总体 性能。

对于不太熟悉ESI的读者,我在这里稍微作一下解释:

ESI是经过代理服务对页面片断进行访问的一种协议,好比你的HTML代码中有一段ESI:

<esi:include src="/top" max-age="45"/>

那么当ESI服务软件(例如Varnish)获取到包含这行代码的HTML以后,会当即向"/top"这个URL作一个额外的HTTP请求,同时把 这个请求的HTML返回数据填充在这个页面里。假设"/top"的返回数据是一段A标签,那么上文的这段HTML代码在显示给用户的时候,就变成了:

<a href="http://for_example.com">for_example.com</a>

固然ESI的功能远不止那么简单,咱们在此主要用到的是它的页面片断缓存功能。

5.数据库操做

做为一个成熟的Web开发框架,对数据库操做的支持天然是重中之重,Symfony2对于这块天然也不例外。Symfony2默认使用 Doctrine2做为其ORM的实现,经过Doctrine2,用户能够像操做一个类同样去操做数据库,从而提升开发效率。ORM这个概念读者应该都不 陌生,在其余语言里也有各类实现,但在使用ORM上咱们常常遇到这样的挑战,即如何权衡数据库性能和开发效率之间的平衡:

  • 直接写SQL语句来操做数据库固然效率最高,但是开发速度慢,动一个字段甚至会对整个项目都须要进行重构。
  • 使用ORM则能够加快开发速度,但每每因为ORM的封装和限制,所生成出来的SQL没法保证运行效率。

那Doctrine2又是如何解决这些问题的呢?咱们经过一个比较有特点的例子来感觉一下:

$user=$this->getDoctrine()->getRepository('ScourgenHFS2Demo1Bundle:User')->find(2)
echo $user->getName();

在上面这段代码中,第一行虽然看起来是向User表去查询ID为2的用户,但其实尚未任何SQL语句在数据库上执行,$user就是一个上文所提 到的代理对象。而当在第二行中,当这个对象的属性第一次被调用的时候,真实的SQL语句才会被传递到服务器,而且将结果集中的name字段返回给echo 函数。

形成以上现象的缘由是Doctrine2支持延迟加载功能:当程序执行对数据库的操做时,好比获取一条条数据,返回的对象并非一个真实的来自数据 库的结果集,而是一个代理对象(Proxy Object),而只有当数据被真正调用的时候,这个代理对象才会去数据库里进行查找,并返回真实的数据。

因此使用延迟加载的好处是:ORM会根据真正须要的内容去获取相应的数据。

想象一下若是你的表结构很是复杂,并且前端页面常常改变,在这种状况下通常都须要对SQL操做进行必定的修改和重构才能知足不断变化的需求,并且很 难保持性能的最优化。而若是此时有延迟加载功能,就可以保证无论页面如何变化,数据库操做相关的代码均可以在不须要修改的前提下,一直生成最优化的SQL语句去获取那些真正被使用到的数据。因此延迟加载才可以一方面显著的加快开发速度,一方面优化页面性能。

固然除此以外Doctrine2的功能还有不少,包括批量处理、对象生命周期管理、表结构自动维护等等,Doctrine2也能够做为一个单独的类 库被Symfony2以外的程序所使用。但在这里因为主题和篇幅的限制,我不作过多的介绍,若是读者们对此感兴趣的话,但愿之后可以有机会单独写文介绍。

6.总结

本文向你们展现了Symfony2的一些很酷的功能,从Symfony2的环境设置,到Assets管理,到开发调试栏的介绍,再到缓存优化的分 析……读者能够经过上面的几个例子感觉到使用Symfony2做为框架进行开发并非一件很复杂的事情:改几行配置,甚至都不须要写代码,就可使用业界 的各类“最佳实践”来解决问题。就让框架作应该作的事情吧,分工明确才能让开发者可以安心的把注意力集中在本身项目的业务逻辑上,从而提升项目的速度和质 量。

而在国内,因为框架辈出,甚至有几年开发经验的工程师甚至都会本身作一个框架,社区和业界也缺乏一个公认的答案和方向:工程师都在打一枪换一个地 方,而各大公司和团队都在重复制造各类不一样样子的轮子,整个业界的风气也变得异常浮躁,以致于许多人索性放弃了对完美的追求,直接认可了他们作的事情就是 “quick and dirty“的。

我并不期望可以改变你的想法,但若是你也有一些相同的感悟,那么请尝试一下Symfony2,它将帮助你从新找回信心。

7.关于做者

洪涛在互联网、零售、电信领域有多年的从业经验,曾负责中国电信域名纠错平台的开发,也曾为雅虎、腾讯等大型互联网站进行架构设计与开发工做,他善 于使用开源技术解决技术难题,做为一个87年生的人,他有着“洪大师”的称号。洪涛目前的兴趣是Symfony2框架在中国的推广和普及。

相关文章
相关标签/搜索