技术开发 频道

优化Web站点资源措施



【IT168 专稿】

雅虎除了用《Web站点优化基本原则》一文中的一些基本原则之外,现在我们看看雅虎还推荐的另外一些什么措施,并且看看这些优化措施如何供我们使用。
使用内容交付网络(Content Delivery Network
一个内容交付网络就是一个网路服务器,在不同的地理位置。每个服务器有一个站点文件的副本。当一个网站访问者浏览你的网站,发送一个请求,这个文件就会传送到离访问者最近的那个服务器上。
这个设置对你的总体性能有一个很大的改观,但是,使用内容交付网络是非常昂贵的。同样地,在你做个人博客的时候是不会采取这种方式的,但是,当你的客户想让你构建一个站点,这个站点会有很大的访问量的时候,那么这就很有用了。一些非常著名的内容交付网络提供商有AkamaiAmazon, 通过它的S3服务。
市场上有一些非盈利的CDN提供商;看看关于CDN Wikipedia文章 了解你的项目是否有资格来使用他们中的一些。举个例子,一个免费的对等CDN是 了解你的项目是否有资格来使用他们中的一些。举个例子,一个免费的对等CDN是Coral CDN,它能非常容易的整合你的站点。对于这个CDN来说,你只需要一个URL和追加一个"nyud.net"后缀到主机名后即可。下面就是一个例子:
http://example.org/logo.png
变成
http://example.org.nyud.net/logo.png
 
拥有不同的域且减少DNS查找
当用户浏览器已经下载了一个页面的HTML,发现还需要许多的其它页面成分,因此又开始下载那些页面成分了。但是浏览器限制同时下载的数量,如HTTP/1.1规范里说的,每个域同时下载的资源(asset)是2个。
因为这个限制存在于每个域的基础上,你能使用结果域来持有你的站点资源,这样就增加了能平行下载的数量了。许多共享主机会允许你创建子域。除非你的主机设置了你能创建的子域的数目(一些限制你最大数目为5),如果没有那么严重,就是你不想利用那么多的子域来看看性能是如何得到显著提升的。
但是,如规则 #9 所述,你应该减少DNS查找的次数,因为这些也是花销很大的。对于那些持有页面资源的每个域和子域,浏览器需要做一个DNS查找。因此你的域越多,你的网站因为DNS的查找就会变得很慢了。雅虎的搜索引擎建议2到4个域是非常好的的选择,但是你可以设置自己觉得你站点最好的数目。
就总的知道方针来说,我建议你使用一个域来持有HTML页面,另外两个持有你的非HTML代码资源。下面就是一个例子:
  • www.sitepoint.com – 持有HTML代码(或者内容图片)
  • i1.sitepoint.com – 持有JS, CSS, 和其它 图片
  • i2.sitepoint.com – 持有网站大多数的图片
不同的主机提供上应该提供不同的界面来创建子域,较为理想的情况他们还应该提供一个选项来指定目录,为子域保存文件。举个例子,如果你的标准域是www.sitepoint.com,并且它指向/home/sitepoint/htdocs目录,那么在理想的情况下,你应该可以创建子域i1.sitepoint.com(要么通过管理控制面板或者通过在文件系统创建符号链接),并且可以指向同样的文件夹,/home/sitepoint/htdocs。这样的话,你能保持所有的文件在同一个位置,正如它们都在你的开发环境中一样,都是在同一个位置的,使用一个子域来引用它们。
但是,一些主机有可能会阻止你创建子域,或者限制你指向文件系统中的特定地点。在这种情况下,你唯一的选择就是位置站点资源到一个新的位置。在这种情况下,不要试着创建重定向,这只会让情况变得更糟糕,因为对于每个图像来说创建了两个请求。
如果你的主机提供上根本不允许创建子域,你就可以有选择购买更多的域,纯粹使用这些域来持有你的站点资源,毕竟,这就是大多大型网站采取的办法。雅虎,使用yimg.com,Amazon有images-amazon.com,SitePoint有sitepointstatic.com。如果你自己有几个站点,或者管理你客户的一大群站点,你或许应该考虑购买两个域,比如说yourdomain-i1.com 和yourdomain-i2.com,使用这些域来持有你维护所有站点的组成成分。



放置资源于自由释放cookie的域上
如果你设置了许多的cookies,页面请求头部增大很多,因此那些cookies和每个请求是一起发送的。另外,你的网站的资源中或许没有用到那些cookies,因此这些信心将会毫无理由的重复发送到客户端。有时候,那些头部甚至比请求还大,这是很费资源的。想想要下载几个小的图标,他们只有半个KB,而请求他们的HTTP头就有1KB.
如果你使用子域来持有你的资源,你需要保证你设置的cookies是为你的标准域名准备的(如www.example.org)而不是为了上级域名而设置的(如example.org)。这样的话,你的子域就会释放cookie。如果你试着来提升一个现有的站点的性能,并且你已经在上级域名上设置cookies,你就可以考虑在一个新的域名上持有你站点的资源,而不是放在子域上。
分布资源到不同的域上
是时候让你决定在i1.example.org放置什么资源,在i2.example.org上放置什么资源了,关于这点没有明确的规定。只需要确保当每个请求来的时候,你不是随机的从两个域上下载资源,因为这会导致相同的资源被下载两次,from i1一次,from i2一次。
你可以根据文件的大小来分割站点资源,或者通过其它的标准,只要根据你的页面情况有意义就好。你也可以选择将所有的内容图片(包括HTML中有<img />标签的)放在i1上,所有的布局图片的内容放在i2上,虽然在一些情况下这种解决方案不是最优的。在这种情况下,浏览器将会下载处理CSS文件,然后根据那些需要应用的规则,将会选择仅仅下载样式表所需要的那些图片。问题在于那些没有CSS引用的图片将没有立即下载,因此服务器上的负载就可能不平衡。
决定如何分配站点资源的最好方法通过实验:你可以使用Firebug的Net面板来监视资源下载的结果,然后觉定如何在不同的域上分配资源,以便加速下载速度。
设置DNS查找
既然你已经决定每页的请求不会超过四个DNS查找,那么在你的网页上整合第三方的内容或许是一件非常聪明的事情,如Flickr网站上的图片或者广告,这些都是在第三方服务器上的。但是,超链接到其它网站上的图片(通过设置你页面的<img />标签,src属性指向其它服务器的文件)不仅盗取了人家的待跨,还会影响你自己页面的性能,因为额外多了一个DNS查找。
如果你的网站包含有用户生成的内容(举个例子,如论坛),你就不能简单的说减少多次DNS查找,因为用户可能发送网上任何地方的图片。你可以写一个脚本复制用户提交的每个图片到你的服务器,但是这种方法是非常复杂的。
举个例子,在phpBB 论坛软件 中,你可以设置用户是否需要链接他们的图片或者上传他们的图片到服务器。在这中情况下,对于你的网站来说,上传他们的图片到服务器有利于提升你网站性能。
使用有过期时间设置的头部
为了追求更好的性能,你的静态资源应该完全静态。这个意思就是说没有动态生成的脚本和样式。或者<img>标签指向生成动态图片的脚本。如果你有这么的一个需求,举个例子,你想生成一个图像包括你网站访问者的用户名。这个动态的效果可以做成“脱机”静态的,结果以静态的隐藏。在这个例子中,你可以在用户登录的时候,立即生成图片。你也可以在文件系统中储存那些图片,将图片路径写到数据库中。另外一个替代的方法就是安排一个自动的处理过程,生成动态成分,然后以静态的文件保存他们。
如果一个页面全部是静态的话,你就可以在头部设置一个过期的时间,因此当你的这个页面已经下载,就在浏览器的缓存器中,不会再进行第二次请求了(或者说要经过很长一段时间,下面我们将会说到)。
设置一个过期的头部在Apache中是很简单的:增加一个.htaccess文件,这个文件包括下面的一些命令,并把它放到i1 和i2 子域根目录文件夹里:
ExpiresActive On
ExpiresDefault "modification plus 10 years"
第一个命令使得Expires头部的生成是可用的。第二个设置了在该文件被修改后,它的过期时间是10年,另外一种解释是当你复制这个文件到服务器后,它的有效期是10年。你也可以使用这个设置"access plus 10 years",意思就是说在用户第一次请求该文件后,过期时间是10年。
如果你想的话,你可以甚至设置每个文件类型的过期时间
ExpiresActive On
ExpiresByType application/x-
javascript "modification plus 2 years"
ExpiresByType text/css "modification plus 5 years"
更多的信息,点击Apache documentation on mod_expires.


资源命名技巧
我们刚才说的那个技术存在这问题,当我们想修改页面的一个资源的时候,如一个图片,就会出现问题。如果你上传了一个修改过的图片到你的web服务器,新的访问者将会接收到更新的图片,但是之前的访问者(跟新图片前)不会接收到更新的图片。他们看到的是旧的版本,因为你已经通知他们的浏览器不要再次请求图片了。
解决方案就是修改资源的名字,但是这就会带来一些维护的故障了。举个例子,如果你有几个CSS样式表的定义,指向img.png,并且你修改了该图片,把它的名字命名为img2.png,你将不得不在引用该图片的样式表中修改所有的指向,并且更形样式表。对一个大点的项目,你或许要考虑系一个工具来自动完成这个任务。
当你对你的资源进行命名的时候,你需要提出一个命名约定。举个例子,你将:
  • 追加时间信息到文件名前,如:img_1185403733.png.
  • 使用你资源控制系统(如cvs或者svn)的版本号来命名,如img_1.1.png.
  • 文件名前自动增加1(如当你看到一个文件名为img1.png,简单的保存修改的图片为img2.png)
这里并没有一个唯一的答案。根据你个人的偏好,具体的页面,项目的大小,和你开发团队的情况,来决定你的命名约定。
如果你使用CVS,这里有一个小的PHP函数,能够帮助你提取存在CVS中的文件的版本。
function getVersion($file) {
   $cmd = 'cvs log -h %s';
   $cmd = sprintf($cmd, $file);
   exec($cmd, $res);
   $version = trim(str_replace('head: ', '', $res[3]));
   return $version;
}
// example use
$file = 'img.png';
$new_file = 'img_' . getVersion($file) . '.png';
使得Etags失效
当你实施上面的选择的时候,就会遇到潜在的争论。但是下面的这条就会很容易了,你只需要增加下面的内容到你的.htaccess文件即可。
没有FileETags
注意这条规则适合那些有单独主机的站点。如果你在使用一个共享主机,我推荐你跳过这个步,下面是推荐的原因:
  • 由于内部的目地,主机的机器会改变
  • 你或许会改变主机
  • 这条规则复杂。
使用CSS子画面
使用一个称为CSS自画面的技术,你可以将几张不同的图片结合成单独一张,随后使用CSS的background-position属性来显示你当前所需要的图片。这个技术不是给内容图片使用(那些在HTML代码中出现在<img />标签中的图片)的,是为装饰用途的图片准备的。这些图片将不会影响到一个页面的可用性,通常在一个样式表中引入,为了保持代码的简约(规则#0)。
下面看一个例子。我们将准备两张图片。第一张是help.png;第二张是rss.png.以这两张为基础,我们可以创建第三张图片,sprite.png,它包括前面两张。
将两张图片组合成一张图片


第三那张图片文件的大小经常会比前面两张图片文件大小之和的小。为了显示第一张图片,我们使用下面的CSS:
#help {
 background-image: url(sprite.png);
 background-position: -8px -8px;
 width: 16px;  
 height: 16px;
}
 
为了显示第二张图片,我们使用下面的CSS:
#rss {
 background-image: url(sprite.png);
 background-position: -8px -40px;
 width: 16px;  
 height: 16px;
}
 
为了尽快生成子画面图片,而不用计算像素坐标,你可以使用我开发的一个工具:CSS 子画面生成器。 更多的关于CSS子画面,一定要读Dave Shea的文章,题目为CSS Sprites: Image Slicing's Kiss of Death.


使用Post-load Pre-loadingInline资源
如果你是一个负责的web开发者,你就已经坚持使用分离的概念了,并且为你的页面内容使用了HTML,CSS表现页面布局,JavaScript产生网页行为。页面中这些决然不同的部分就应该一直保存在不同的文件中。但是为了性能的原因,你有时候在设计开发首页的时候,考虑打破这些规则。首页应该是你网站中打开最快的页面,如果访问者发现首页加载很面,无论它的内容多么好,许多第一次访问的浏览者会离开你的网站。
当一个访问者访问你的网站的时候,这是他第一此访问,没有缓存你网页的内容,最好的方来传送你的页面的方法就是只需一个请求,没有分离的部分。这就意味着脚本和样式表需要内嵌。实际上也可能让图片内嵌(尽管在浏览器中不支持),不过这就扯远了。刚才那么做的话,除了语义不正确的话,使用脚本和样式也阻止了缓存网页的组件,因此一个好的策略就是在首页已经加载后,再去加载组件。有一个技术名字很令人迷惑的技术叫:post-load preloading。让我们看一个例子。
让我们假设有一个文件,包含你首页的内容,叫做home.html,而其它的HTLM文件包括的内容零散的分布在整俄站点的文件中,并且所有这些内容页面使用一个JavaScript文件,mystuff.js,而首页只需要这个文件中的一小部分而已。
你要使用的策略就是将首页要用到的JavaScript移除出mystuff.js文件,并且将它内嵌到home.html中。接着,一旦home.html完全加载,使用一个幕后的请求预加载mystuff.js。通过这种方式,当用户点击你的内容页面时,JavaScript已经加载到浏览器,并且已经缓存了。
这个技术已经被一些大家所采用:雅虎和Google已经内嵌脚本和样式表到他们的主页中了,并且已经使用了预加载。如果你访问Google的首页,它下载一些HTML和一个单独的图片,也就是它的Logo。接着,一旦它的主页完成加载,就有一个请求获取字画面,这个子画面不到第二页面加载是不需要的,第二个页面也就是显示搜索结果的页面。
雅虎搜索页面执行有条件的预加载,这个页面并不是自动加载额外资源,而是等待用户在搜索框中输入搜索关键字。一旦你开始输入关键字,这就几乎保证你将要提交一个搜索请求。接着你确实这么做了,你将会得到一个结果页面,包括一些已经为你缓存的部分了。
预加载一个图片可以用下面几行JavaScript代码做到:
new Image().src='image.png';
预加载JavaScript文件,使用JavaScript,包括DOM技术,并且还要创建一个新的<script>标签,如:
var js = document.createElement('script');
js.src = 'mysftuff.js';
document.getElementsByTagName('head')[0].appendChild(js);
下面是CSS版本:
var css  = document.createElement('link');
css.href = 'mystyle.css';
css.rel  = 'stylesheet';
document.getElementsByTagName('head')[0].appendChild(css);
在第一个样本中,图片被请求了,但是从来没有使用,因此它不影响当前页面。在第二个样本中,脚本被加入到页面中,因此也被下载了,它就将会被分析执行。对于CSS也是同样的道理,它将应用到页面。如果这是不和要求的,你仍旧可以使用XMLHttpRequest预加载资源。
 
对内容进行gzip压缩
大多数现代的浏览器都能支持压缩内容,因此,一个很好的性能页面因该让它里面的所有内容压缩。因为大多数的图片,swf文件和其它的多媒体文件已经被压缩过了,你不需要担心如何压缩它们。
但是,你需要非常小心被压缩过的HTML,CSS,客户端脚本,和其它任何类型的文本内容。如果你向服务器提交返回XML 的XMLHttpRequests,你就应该确保你的服务器gzips这些内容。
如果你打开Firebug中的Net面板(或者使用LiveHTTPHeaders),你可以看到它们的内容都被压缩过,你查看响应的Content-Encoding头部就会发现。如下面的例子所示:
Request请求示例:
GET /2.2.2/build/utilities/utilities.js HTTP/1.1
Host: yui.yahooapis.com
User-Agent:
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.5) Gecko/20070713 Firefox/2.0.0.5
Accept-Encoding: gzip,deflate
响应示例:
HTTP/1.x 200 OK
Last-Modified: Wed, 18 Apr 2007 17:36:33 GMT
Vary: Accept-Encoding
Content-Type: application/x-javascript
Content-Encoding: gzip
Cache-Control: max-age=306470616
Expires: Sun, 16 Apr 2017 00:01:52 GMT
Date: Mon, 30 Jul 2007 21:18:16 GMT
Content-Length: 22657
Connection: keep-alive
在这个请求中,浏览器通知服务器它能支持压缩文件,和压缩编码(Accept-Encoding: gzip,deflate),服务器响应了gzip编码的内容(Content-Encoding: gzip).
当进行gzip压缩内容的时候遇到一个问题:你必须保证代理商不妨碍你。如果一个ISP的代理缓存了你的gzip压缩内容,并把它面向它的所有客户,那么一些不支持压缩内容的浏览器也会看到压缩的内容。
为了避免这种问题,你可以使用带有Vary: Accept-Encoding的响应头部,来告诉你的代理商只有发送同样的带有Accept-Encoding头部请求的,才高速缓存这些响应。在上面的那个例子中,浏览器说他支持gzip和deflate,并且服务器响应为代理提供一些额外的信息,声明对于任何发送同样Accept-Encoding 内容的请求的客户点,gzip编码的内容也是可以的。
这里有另外一个问题:一些浏览器(举个例子,IE 5.5, IE 6 SP 1)声称他们gzip压缩内容,但是当你用他们浏览时就会遇到问题(正如Microsoft downloads sitesupport site所描述的那样)。如果你很在一使用使用这样浏览器的人(他们占到一个网站访问者总数的1%一下)的话,你可以使用一个不同的头部。在头部标记Cache-Control: Private,这就能消除代理进行高速缓存。另外一个方式就是使用头部Vary: *.来阻止代理高速缓存。
如果你对Accept-Encoding还是很迷惑的话,那么就考虑用deflate,作为第二种方法来编码内容,虽然在众多浏览器中不是很流行,但是也很效果,因此gzip被引用进来了。
总结
到现在为止,你已经知道很多关于如何优化一个站点项目的方法了(最重要的是,在构建你的站点的时候会考虑性能问题)。当提及到优化,记住总的经验法则,集中那些影响大的方面,而不是小的优化方面。
你或许选择不要去实现我们以上讨论的,但是你仍旧可以集中下面几条,也会产生很好的效果。
·产生很少的HTTP请求,通过组合页面部分,包括JavaScript文件,样式表,图片(使用CSS子画面)
·将文本内容,包括HTML、scripts、风格、XML 、JSON,纯文本压缩
·缩小化 ,将脚本置于底部,样式表置于顶部
·使用独立的自由释放cookie的域保存站点组成成分。
祝你优化工作好运,当你看到结果时,你就会很有成就感。
0
相关文章