Introduction to Website Performance

2018-02-28

减少HTTP请求

  • 图片地图

    • 通过 HTML map 标签实现将用户的点击映射到一个操作。
      1
      2
      3
      4
      5
      6
      7
      8
      <img usemap='#map1' border=0 src='images/imagemap.gif?t=1196816255'>
      <map name='map1'>
      <area shape='rect' coords='0,0,31,31' href='home.html'title="Home">
      <area shape='rect' coords='36,0,66,31' href='gifts.html' title="Gifts">
      <area shape="rect" coords="71,0,101,31" href="cart" title="Cart">
      <area shape="rect" coords="106,0,136,31" href="settings.html" title="Settings">
      <aare shape="rect" coords="141,0,171,31" href="help.html" title="Help">
      </map>
  • CSS Sprites

    • 使用一个DIV包含五个链接,将所有图片合并成一个图片作为DIV的背景图片,五个链接分别用SPAN表示,每个具有不同的类,通过background-position属性指定CSS Sprites的偏移量。
  • 内联图片
    • 使用data:URL模式可以在Web页面中包含图片但无需任何额外的HTTP请求。
  • 合并脚本和样式表
    • 通过Webpack将脚本打包成一个统一脚本
    • 网站由于可能存在多个页面,每个页面可能要求使用不同脚本,由此可能不同的脚本组合会有不同的打包方式, 打包方式会逐渐变复杂。 可以通过Webpack打包形成按需加载。

使用内容发布网络(CDN)

  • 什么是内容发布网络(CDN)?
    • 内容发布网络是一组分布在多个不同地理位置的Web服务器,用于更加有效地向用户发布内容。
    • 无论如何也不要使用HTTP重定向来将用户指向指定服务器。
    • 依赖CDN的一个缺点是你的响应时间可能会受到其他网站的流量影响。
    • 另一个缺点是无法直接控制组件服务器所带来的麻烦。
    • 如果CDN服务器的性能下降,你网站的性能也会下降。
    • CDN用于发布静态内容——图片、脚本、样式表、Flash等, 提供动态HTML页面会引入特殊的存储需求。
  • 减少网站响应时间

添加Expires头

  • Expires头
    • Web服务器使用Expires头来告诉Web客户端它可以使用一个组件的当前副本直到指定的时间为止。
    • 长久的Expires头常用于图片,但应该将所有的组件上,包括脚本、样式表、Flash。
    • 缺点: 要求服务器和客户端的时钟严格同步。
    • 缺点: 过期时间需要经常检查,一旦未来这一天到来,还需要在服务器配置中提供一个新的日期。
  • Max-Age 和 mod_expires

    • Cache-Control使用max-age指令指定组件被缓存多久,它以秒为单位。如果从组件被请求开始过去的秒数少于max-age,浏览器就使用缓存版本。
    • max-age和Expires两个响应头同时出现时,HTTP规范规定max-age指令将重写Expires头。
    • mod_expires Apache模块使你在使用Expires头时能够像使用max-age那样以相对的方式设置日期。这通过Expires-Default指令完成。
    • 跨浏览器改善缓存的最佳解决方案就是使用由ExpiresDefault设置的Expires头。
  • 空缓存 VS 完整缓存

    • 空缓存:如果你的页面中的则缓存为“空”。
    • 完整缓存:如果你的页面中的可缓存组件都在缓存中,则为完整缓存。
    • 空缓存或完整缓存页面浏览的数量取决于Web应用程序的本质。
  • 不仅仅是图片
    • HTML文档不应该使用长久的Expires头,因为它包含动态内容, 这些内容在每次用户请求时都被更新。
    • 我们可以通过监控网站组件的Last-modified属性来统计哪些组件的更新频率比较低,可以为更新频率低的组件设置Expire头。
  • 修订文件名
    • 如果我们将组件配置为可以由浏览器代理缓存, 当这些组件改变时用户如何获得更新呢?
    • 当出现了Expires头时,直到过期日期为止一直会使用缓存的版本,浏览器不会检查任何更新,直到过了过期日期。 这也是为什么使用Expires头能够显著减少响应时间,浏览器直接从硬盘上读取组件而无需产生任何HTTP流量。因此即使在服务器上更新了组件,已经访问过的用户也不大可能获取最新的组件。
    • 最有效的解决方案是修改其所有链接, 全新的请求将从原始服务器下载最新内容,对于PHP和Perl等动态语言生成HTML页面,简单的解决方案是为所有组件的文件名使用变量(将版本号或者修改日期作为文件名的一部分)。

压缩组件(通过减少HTTP响应大小来减少响应时间)

  • 压缩是如何工作的?
    • 自HTTP1.1开始,Web客户端可以通过HTTP请求中的Accept-Encoding头来表示对压缩的支持。
    • Web服务器通过响应中的Content-Encoding头部来通知Web客户端。
    • gzip是目前最理想的压缩方式。
      1
      Accept-Encoding: gzip deflate
1
Content-Encoding: gzip
  • 压缩什么
    • 很多网站压缩HTML文档,压缩脚本和样式表也非常常见。
    • 压缩需要耗费额外的CPU周期来完成压缩,客户端要对压缩文件进行解压。
    • 通常大于1KB或者2KB的文件进行压缩。
    • mod_gzip_minimum_file_size指令控制着希望压缩文件的最小值,默认值是500B。
  • 节省
    • 压缩通常能将响应的数据量减少将近70%。
  • 配置

    • Apache 1.3 ——mod_gzip
      • Apache 1.3的gzip压缩由mod_gzip模块提供
      • mod_gzip_on 启动mod_gzip
      • mod_gzip_item_includemod_gzip_item_exclude 基于文件类型、MIME类型、用户代理等定义哪些需要压缩,哪些不需要压缩。
      • 最需要的修改就是在配置中明确指出需要明确压缩脚本和样式表。
      • mod_gzip_can_negotiate和mod_gzip_update_static指令可以将保存压缩过的内容自动保存在磁盘上, 并在原内容发生变化时更新压缩过的内容。
    • Apache 2.x ——mod_deflate
      • 压缩通过mod_deflate完成,但也是gzip压缩。
  • 代理缓存

    • 当浏览器通过代理来发送请求时,假设针对某个URL发送到代理的第一个请求来自一个不支持gzip的浏览器,这是到达代理的第一个请求,因此其缓存为空,代理会将该请求发送给服务器。此时服务器的响应是未经过压缩的。这个没有压缩的响应被代理缓存起来并发给浏览器。现在,假设到达代理的第二个请求是同一个URL, 来自一个支持gzip的浏览器,代理会使用其缓存中未经压缩的内容进行响应。失去了压缩的机会。
    • 可以在Web服务器的响应中添加Vary头,这使得代理缓存响应的多个版本,为Accept-Encoding 请求头的每个值缓存一份。
  • 边缘情况
    • 老版本浏览器不支持gzip压缩, 可以使用mod_gzip_item_include reqheader "User-Agent: MISE[6-9]来指定浏览器白名单。
    • 当与代理缓存结合考虑时情况变得更加复杂。最好使用Cache-Control: private来禁止所有浏览器使用代理缓存。

将样式表放在顶部

  • 逐步呈现
    • 将样式表放在文档底部会导致在浏览器中阻止内容逐步呈现。为避免当样式变化时重绘页面中的元素,浏览器会阻塞内容逐步呈现。由此会产生白屏现象。
  • sleep.cgi

  • 白屏

    • 将样式表放在文档底部延迟页面加载的问题只发生在IE浏览器中。当发生这种现象时页面完全空白,直到页面所有内容同时涌上屏幕。
    • 将样式表放在顶部HEAD中解决白屏问题,引入样式表的方式有两种:使用LINK标签或者@import规则。
    • @import规则引入样式表会导致组件下载的无序性,即使在顶部也可能会产生白屏问题。
  • 无样式内容的闪烁

    • 如果样式表仍在加载,构建呈现树就是一种浪费,因为在所有样式表加载并解析完毕之前无需绘制任何东西,否则,在其准备好之前显示的内容会重新呈现, 造成闪烁( FOUC Flash of Unstyled Content)。
    • IE在新窗口中加载页面、重新加载和作为主页时,会发生白屏。单击链接、使用书签或者键入URL时,IE选择FOUC, Firfox则总是选择FOUC
    • 为避免白屏和FOUC请总是在Head标签中使用LINK标签引入样式表。

将脚本放在底部

  • 将脚本放在中、上部带来的问题:
    • 所有位于脚本下方的组件只有在脚本完成下载之后才会开始下载。
    • 所有位于脚本下方的组件只有在脚本完成下载之后才会开始渲染。
  • 并行下载
    • HTTP1.1规范建议每个主机名并行地下载两个组件。、
    • IE 和 Firefox都遵循这一建议。
  • 脚本阻塞下载
    • 下载脚本时,并行下载被禁用,即使使用了不同的主机名,其中一个原因是,脚本中可能使用document.write来修改页面内容,因此浏览器会等待,以确保页面能够恰当地布局。
    • 阻塞并行下载,也是防止并行下载脚本导致脚本执行的顺序错误。
    • 脚本设置DEFER属性表明脚本不包含document.write则不会阻碍并行下载。(不包含Firefox)
  • 脚本放在顶部导致页面内容下载被阻塞造成白屏现象。

避免CSS表达式

  • CSS表达式只在IE中工作。
  • CSS表达式是动态设置CSS属性的一种方式。
  • background-color: expression( (new Date()).getHour() % 2 ? "#B8D4FF" : "#F08A00"); 动态设置背景颜色,设置为每小时更新一次。
  • Expression 方法接受一个JavaScript表达式,CSS属性被设置为对JavaScript表达式求值的结果。
  • 对CSS表达式的频繁求值(在页面呈现,大小改变,页面滚动,鼠标移动)导致性能低下。

  • 一次性表达式

    • 使用CSS表达式必须求值一次, 可以在这一次执行中重写其本身。
  • 事件处理器
    • 通过设置事件处理器来设定CSS属性值。

使用外部JavaScript和CSS

  • 页面查看
    • 每个用户产生的页面查看越小, 内联JavaScript和CSS的论据越强势。
  • 空缓存 VS 完整缓存
  • 组件重用
    • 每个用户每月会话数量较高,普通用户在一个会话中访问多个不同页面的网站: 适合联合所有JavaScript和CSS到一个文件中。
    • 对于普通用户只访问一个页面和很少的跨页访问的网站来说:为每个页面单独提供一个分离的外部组件。
    • 折中的方法就是将你的页面分成几种页面类型,然后为每种页面类型创建单独的脚本和样式表。
  • 典型的对比结果
  • 两全其美

    • 加载后下载——通过onload事件实现
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <script>
      function doOnload(){
      setTimeout("downloadComponents()", 1000);
      }
      window.onload = doOnload;

      function downloadComponents(){
      downloadJS("http://stevesouders.com/examples/testsma.js");
      downloadCSS("http://stevesouders.com/testsm.css");
      }
      function downloadJS(url){
      var elem = document.createElement("script");
      elem.src = url;
      document.body.appendChild(elem);
      }
      function downloadCSS(url){
      var elem = document.createElement("link");
      elem.rel = "stylesheet";
      elem.type = "text/css";
      elem.href = url;
      document.body.appendChild(elem);
      }
      </script>
  • 动态内联

    • 服务器可以用cookies做指示,如果cookie不存在,就采用内联JavaScript和CSS,如果cookie出现了,则可能外部组件位于浏览器的缓存中,并使用了外部文件。
    • 由于每个用户开始的时候都没有cookie,因此必须有一种途径来引导这一过程。这可以通过使用前一个例子中的加载后下载技术来完成。

减少DNS查找