Google Maps、Yahoo! Mail、Facebook、MySpace、YouTube 和 Amazon 都是为扩展而构建的网站示例。它们访问 PB 级数据,以每秒 TB 级的速度发送给全球数百万用户。其规模令人叹为观止。
用户从更狭隘的角度看待这些大型网站。典型的用户有 MB 级的数据,以每秒几百 KB 的速度下载。用户对每秒处理的大量请求不太感兴趣;他们更关心自己的个人请求。当他们使用这些 Web 应用程序时,他们不可避免地会问同样的问题:“为什么这个网站这么慢?”
答案取决于开发团队将性能改进重点放在哪里。为了可扩展性而进行的性能优化理所当然地侧重于后端。数据库调优、复制架构、定制数据缓存等等,使 Web 服务器能够处理更多的请求。这种效率的提高转化为硬件成本、数据中心机架空间和功耗的降低。但是,后端在延迟方面对用户体验有多大影响呢?
此处列出的 Web 应用程序是世界上经过高度优化的应用程序,但它们的加载速度仍然比我们希望的要慢。似乎后端的高速存储和优化的应用程序代码对最终用户的响应时间几乎没有影响。因此,为了解释这些加载缓慢的页面,我们必须关注后端以外的其他方面:我们必须关注前端。
图 1 说明了当您的浏览器访问 iGoogle 且缓存为空时发送的 HTTP 流量。每个 HTTP 请求都由一条水平条表示,水平条的位置和大小基于请求开始的时间和持续时间。第一个 HTTP 请求是针对 HTML 文档 (http://www.google.com/ig)。
如图 1 所示,HTML 文档的请求仅占整个页面加载时间的 9%。这包括从浏览器发送到服务器的请求时间,服务器在后端收集所有必要信息并将其拼接为 HTML 的时间,以及将 HTML 发送回浏览器的时间。
其余 91% 的时间花费在前端,其中包括 HTML 文档命令浏览器执行的所有操作。其中很大一部分是获取资源。对于此 iGoogle 页面,还有 22 个额外的 HTTP 请求:两个脚本、一个样式表、一个 iframe 和 18 个图像。HTTP 配置文件中的空白(没有网络流量的位置)是浏览器解析 CSS(层叠样式表)以及解析和执行 JavaScript 的地方。
图 2 显示了 iGoogle 的预热缓存情况。这里只有两个 HTTP 请求:一个用于 HTML 文档,另一个用于动态脚本。空白甚至更大,因为它包括从磁盘读取缓存资源的时间。即使在预热缓存的情况下,HTML 文档也仅占整个页面加载时间的 17%。
这种情况,即大部分页面加载时间花费在前端,适用于大多数网站。表 1 显示,美国排名前 10 位的网站(如 Alexa.com 所列)中,有 8 个网站最终用户的响应时间少于 20% 用于从后端获取 HTML 文档。两个例外是 Google 搜索和 Live 搜索,它们都经过高度优化。这两个站点在空缓存情况下下载四个或更少的资源,而在预热缓存情况下仅下载一个请求。
生成 HTML 文档的时间会影响整体延迟,但对于大多数网站来说,这种后端时间与前端花费的时间相比相形见绌。如果目标是加快用户体验,那么重点应该放在前端。鉴于这一新的重点,下一步是确定改进前端性能的最佳实践。
通过研究和与开发团队的咨询,我开发了一套已被证明可以加快 Web 页面速度的性能改进措施。作为 Harvey Penick 的Little Red Book1 的忠实粉丝,其中包含诸如“瞄准目标”之类的建议,我着手将这些最佳实践总结成一个简单易记的列表。该列表已发展为包含以下 14 条规则(按优先级排序)
我的著作《高性能网站》2 详细解释了每条规则的基础。下面是每条规则的简要总结。
规则 1:减少 HTTP 请求数量。 随着页面中资源数量的增加,整体页面加载时间也会增加。HTTP/1.1 规范中建议,大多数浏览器一次只从给定的主机名下载两个资源,这加剧了这种情况(http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4;较新的浏览器每个主机名打开的连接数超过两个,包括 Internet Explorer 8 [六个]、Firefox 3 [六个]、Safari 3 [四个] 和 Opera 9 [四个])。存在多种技术可以在不减少页面内容的情况下减少 HTTP 请求的数量
规则 2:使用 CDN。 CDN(内容分发网络)是分布式 Web 服务器的集合,用于更有效地向用户交付内容。示例包括 Akamai Technologies、Limelight Networks、SAVVIS 和 Panther Express。CDN 提供的最主要的性能优势是从地理位置上更靠近最终用户的服务器交付静态资源。其他好处包括备份、缓存以及更好地吸收流量峰值的能力。
规则 3:添加 Expires 标头。 当用户访问网页时,浏览器会下载并缓存页面的资源。下次用户访问该页面时,浏览器会检查是否可以从其缓存中提供任何资源,从而避免耗时的 HTTP 请求。浏览器根据资源的过期日期做出决定。如果存在过期日期,并且该日期在未来,则从磁盘读取资源。如果没有过期日期,或者该日期已过时,则浏览器会发出代价高昂的 HTTP 请求。Web 开发人员可以通过指定未来明确的过期日期来获得这种性能提升。这可以通过 Expires HTTP 响应标头来完成,例如以下内容
Expires: Thu, 1 Jan 2015 20:00:00 GMT
规则 4:压缩组件 (Gzip)。 通过网络传输的数据量会影响响应时间,尤其是对于网络连接速度慢的用户。几十年来,开发人员一直使用压缩来减小文件大小。相同的技术可用于减小通过互联网发送的数据大小。许多 Web 服务器和 Web 托管服务默认启用 HTML 文档的压缩,但压缩不应止步于此。开发人员还应压缩其他类型的文本响应,例如脚本、样式表、XML 和 JSON 等。GNU zip (gzip) 是最流行的压缩技术。它通常将数据大小减少 70%。
规则 5:将样式表放在顶部。 样式表告知浏览器如何格式化页面中的元素。如果样式表包含在页面的较低位置,则会出现一个问题:浏览器应该如何处理在样式表下载完成之前可以呈现的元素?
Internet Explorer 使用的一种答案是延迟呈现页面中的元素,直到所有样式表都下载完成。但是,这会导致页面看起来空白的时间更长,给用户页面速度慢的印象。Firefox 使用的另一种答案是呈现页面元素,并在样式表更改初始格式时稍后重新绘制它们。这会导致页面中的元素在重新绘制时“闪烁”,这对用户来说是破坏性的。最好的答案是避免将样式表包含在页面的较低位置,而是将它们加载到文档的 HEAD 中。
规则 6:将脚本放在底部。 外部脚本(通常是 .js 文件)对性能的影响比其他资源更大,原因有两个。首先,一旦浏览器开始下载脚本,它就不会启动任何其他并行下载。其次,在脚本下载完成之前,浏览器不会呈现脚本下方的任何元素。当脚本放置在页面顶部附近(例如在 HEAD 部分中)时,这两种影响都会感受到。页面中的其他资源(例如图像)会被延迟下载,并且页面中已存在的元素(例如文档本身中的 HTML 文本)在之前的脚本完成之前不会显示。将脚本移到页面的较低位置可以避免这些问题。
规则 7:避免 CSS 表达式。 CSS 表达式是在 Internet Explorer 中动态设置 CSS 属性的一种方法。它们允许根据执行嵌入在样式声明中的 JavaScript 代码的结果来设置样式的属性。CSS 表达式的问题在于,它们的评估频率比人们预期的要高得多,在单个页面加载期间可能数千次。如果 JavaScript 代码效率低下,则可能会导致页面加载速度变慢。
规则 8:将 JavaScript 和 CSS 设为外部文件。 可以将 JavaScript 作为内联脚本添加到页面中
<script type="text/javascript">
var foo="bar";
</script>
或作为外部脚本
<script src="foo.js" type="text/javascript"></script>
同样,CSS 可以作为内联样式块或外部样式表包含在内。从性能角度来看,哪种更好?
HTML 文档通常不缓存,因为它们的内容不断变化。JavaScript 和 CSS 的动态性较差,通常几周或几个月都不会更改。内联 JavaScript 和 CSS 会导致在每个页面视图中下载相同的字节(未更改)。这对响应时间产生负面影响,并增加了数据中心使用的带宽。对于大多数网站来说,最好通过外部文件提供 JavaScript 和 CSS,同时使用 Rule 3 中解释的“远期过期”Expires 标头使它们可缓存。
规则 9:减少 DNS 查询。 DNS(域名系统)就像电话簿:它将主机名映射到 IP 地址。主机名更容易让人理解,但 IP 地址是浏览器建立与 Web 服务器连接所需的地址。Web 页面中使用的每个主机名都必须使用 DNS 解析。这些 DNS 查询需要付出代价;每次查询可能需要 20-100 毫秒。因此,最好减少 Web 页面中使用的唯一主机名的数量。
规则 10:压缩 JavaScript 代码。 如 Rule 4 中所述,压缩是减小通过互联网传输的文本文件大小的最佳方法。通过压缩代码可以进一步减小 JavaScript 的大小。压缩会从代码中删除不需要的字符(注释、制表符、换行符、多余的空格等等),并且通常将 JavaScript 的大小减少 20%。外部脚本应进行压缩,但内联脚本也受益于这种大小减小。
规则 11:避免重定向。 重定向用于将用户从一个 URL 映射到另一个 URL。它们易于实现,并且在真实 URL 太长或太复杂而用户无法记住,或者 URL 已更改时非常有用。缺点是重定向在用户和内容之间插入了一个额外的 HTTP 往返。在许多情况下,可以通过一些额外的工作来避免重定向。如果重定向确实是必要的,请确保使用“远期过期”Expires 标头(请参阅 Rule 3)发出重定向,以便用户在以后的访问中可以避免这种延迟。(某些浏览器不支持缓存重定向。)
规则 12:删除重复脚本。 如果在页面中多次包含外部脚本,则浏览器必须多次解析和执行相同的代码。在某些情况下,浏览器会多次请求该文件。这是低效的,并且会导致页面加载速度变慢。您可能会认为这种明显的错误并不常见,但在对美国网站的审查中,在前 10 个网站中有两个网站发现了这种错误。拥有大量脚本和开发人员的网站最有可能遭受此问题。
规则 13:配置 ETags。 ETag(实体标签)是 Web 客户端和服务器用于验证缓存资源是否有效的机制。换句话说,浏览器缓存中的资源(图像、脚本和样式表等)是否与服务器上的资源匹配?如果是,则服务器只需返回 304 Not Modified 状态,告知浏览器使用其本地缓存的副本,而不是(再次)传输整个文件。在 HTTP/1.0 中,有效性检查基于资源的 Last-Modified 日期:如果缓存文件的日期与服务器上的文件匹配,则验证成功。HTTP/1.1 中引入了 ETag,以允许基于其他信息(例如版本号和校验和)的验证方案。
ETag 并非没有代价。它们会向 HTTP 请求和响应添加额外的标头。如果在多个服务器上托管网站,则 Apache 和 IIS(Internet Information Services)中使用的默认 ETag 语法很可能导致验证错误地失败。这些成本会影响性能,使页面速度变慢并增加 Web 服务器的负载。这是不必要的性能损失,因为大多数网站都没有利用 ETag 的高级功能,而是依赖 Last-Modified 日期作为验证手段。默认情况下,流行的 Web 服务器(包括 Apache 和 IIS)中启用了 ETag。如果您的网站不使用 ETag,则最好在 Web 服务器中将其关闭。在 Apache 中,只需将 FileETag none 添加到您的配置文件即可完成此操作。
规则 14:使 Ajax 可缓存。 许多流行的网站正在转向 Web 2.0,并已开始整合 Ajax。Ajax 请求涉及获取通常是动态的、个性化的或两者兼有的数据。在 Web 1.0 世界中,此数据由用户访问指定的 URL 并返回 HTML 文档来提供。由于 HTML 文档的 URL 是固定的(已添加书签、已链接到等等),因此开发人员必须确保浏览器不缓存响应。
Ajax 响应并非如此。Ajax 请求的 URL 包含在 HTML 文档中;它没有添加书签或链接到。开发人员可以自由地在生成页面时更改 Ajax 请求的 URL。这使开发人员可以使 Ajax 响应可缓存。如果 Ajax 数据的更新版本可用,则通过向 Ajax URL 添加动态变量来避免使用缓存版本。例如,用户地址簿的 Ajax 请求可以在 URL 中包含上次编辑时间作为参数,“&edit=1218050433”。只要用户没有编辑地址簿,就可以继续使用以前缓存的 Ajax 响应,从而加快页面速度。
推广这些性能最佳实践是一项挑战。我能够通过会议、培训课程、咨询和文档来分享这些信息。即使掌握了这些知识,仍然需要花费数小时在数据包嗅探器中加载页面并读取 HTML 才能确定适当的性能改进集。更好的选择是将这种专业知识编纂到一个任何人都可以运行的工具中,从而降低学习曲线并提高这些性能最佳实践的采用率。这就是 YSlow (http://developer.yahoo.com/yslow/) 的灵感来源。
YSlow 是一种性能分析工具,它回答了引言中提出的问题:“为什么这个网站这么慢?”我创建 YSlow 的目的是让任何 Web 开发人员都可以快速轻松地将性能规则应用于他们自己的网站,并具体找出需要改进的地方。它在 Firefox 中作为 Firebug (http://getfirebug.com/) 的扩展程序运行,Firebug 是许多 Web 开发人员的首选工具。
图 3 中的屏幕截图显示了加载了 iGoogle 的 Firefox。Firebug 在窗口的下半部分打开,带有 Console、HTML、CSS、Script、DOM 和 Net 选项卡。安装 YSlow 后,会添加 YSlow 选项卡。单击 YSlow 的 Performance 按钮会启动针对一组规则的页面分析,从而得出页面的加权分数。
如图 3 所示,YSlow 解释了每个规则的结果,并提供了有关如何修复的详细信息。YSlow 屏幕中的每个规则都是指向配套网站的链接,您可以在该网站上找到有关该规则的其他信息。
Web 2.0 承诺未来开发人员可以构建提供类似于桌面应用程序体验的 Web 应用程序。Web 2.0 应用程序是使用 JavaScript 构建的,这带来了重大的性能挑战,因为 JavaScript 会阻止浏览器中的下载和呈现。为了构建更快的 Web 2.0 应用程序,开发人员应使用以下准则解决这些性能问题
拆分初始负载。Web 2.0 应用程序仅涉及单个页面加载。Web 2.0 应用程序不像 Web 1.0 那样为用户请求的每个操作或信息加载更多页面,而是使用 Ajax 在后台发出 HTTP 请求并适当地更新用户界面。这意味着下载的某些 JavaScript 并不会立即使用,而是用于提供用户将来可能需要的功能。问题在于,JavaScript 的这一子集会阻止立即使用的其他内容,从而为了可能永远不会使用的未来功能而延迟立即显示的内容。
表 2 显示,对于美国排名前 10 位的网站,平均有 74% 的下载功能不会立即使用。为了利用这个机会,Web 开发人员应将其 JavaScript 负载拆分为两个脚本:立即使用的代码(约 26%)和用于附加功能的代码(约 74%)。第一个脚本应像今天一样下载,但由于其尺寸减小,初始页面将加载得更快。第二个脚本应延迟加载,这意味着在初始页面完全呈现后,将使用下一节中列出的技术之一动态下载第二个脚本。
无阻塞地加载脚本。如规则 6 中所述,外部脚本会阻止页面中其他内容的下载和呈现。当以典型方式加载脚本时,情况确实如此
<script src="foo.js" type="text/javascript"></script>
但是,有几种下载脚本的技术可以避免这种阻塞行为
您可以在 Cuzillion (http://stevesouders.com/cuzillion/) 中看到这些技术的说明,但作为示例,让我们看一下脚本 DOM 元素方法
<script type="text/javascript">
var se = document.createElement(‘script');
se.src = ‘http://anydomain.com/foo.js';
document.getElementsByTagName(‘head')[0].appendChild(se);
</script>
创建一个新的 DOM 元素,它是一个脚本。src 属性设置为脚本的 URL。将其附加到文档的头部会导致脚本被下载、解析和执行。当以这种方式加载脚本时,它们不会阻止页面中其他内容的下载和呈现。
不要分散内联脚本。关于 JavaScript 性能的前两个最佳实践与外部脚本有关。内联脚本也会影响性能,有时会以显着且意想不到的方式影响性能。关于内联脚本的最重要准则是避免样式表后跟内联脚本。
图 4 显示了 http://www.msn.com/ 的一些 HTTP 流量。我们看到四个样式表请求并行下载,然后有一个空白,之后四个图像也并行下载。为什么不是全部八个并行下载?(请注意,这些请求是在不同的主机名上发出的,因此不受某些浏览器的每个服务器两个连接的限制,如规则 1 中所述。)
此页面在第四个样式表之后包含一个内联脚本。将此内联脚本移动到样式表上方或图像之后将导致所有八个请求并行发生,从而将整体下载时间缩短一半。相反,图像被阻止下载,直到内联脚本执行,并且内联脚本被阻止执行,直到样式表完成下载。这似乎是一个罕见的问题,但它影响了美国排名前 10 位的网站中的四个:eBay、MSN、MySpace 和 Wikipedia。
此时,我希望您对构建高性能网站着迷。我已经解释了快速网站的重要性,展示了将性能工作重点放在哪里,提出了使您的网站更快的具体最佳实践,并推荐了一个您可以用来找出需要修复的内容的工具。但是,当您明天回到工作岗位,面对漫长的任务清单并被催促添加更多功能而不是改进性能时,会发生什么情况?重要的是退后一步,看看性能如何融入更大的图景。
速度是可以用于竞争优势的因素。更好的功能和更具吸引力的用户界面也是区分因素。不必非此即彼。分享这些性能最佳实践的目的是让我们都可以尽可能快地构建网站,无论它们是精简版还是功能丰富版。
我告诉开发人员“人生苦短,编写快速代码!”这可以有两种解释。编写快速执行的代码可以为我们的用户节省时间。对于大型网站,节省的时间加起来相当于用户活动的生命周期。另一种解释迎合了我们对自身工作的自豪感。快速代码是开发人员的荣誉勋章。
性能必须是 Web 开发固有的考虑因素。此处描述的性能最佳实践已被证明有效。如果您想让您的网站更快,请关注前端,运行 YSlow,并应用这些规则。谁知道呢?速度可能会成为您网站最受欢迎的功能。
STEVE SOUDERS (http://stevesouders.com) 在 Google 从事 Web 性能和开源计划方面的工作。他是《高性能网站》的作者,也是 YSlow、Cuzillion 和 Hammerhead 的创建者。他在斯坦福大学任教,并且是 Firebug 工作组的联合创始人。
最初发表于 Queue vol. 6, no. 6—
在 数字图书馆 中评论本文
Niklas Blum, Serge Lachapelle, Harald Alvestrand - WebRTC - 面向开放 Web 平台的实时通信
在这个疫情时期,世界比以往任何时候都更加依赖基于互联网的 RTC(实时通信)。在过去十年中,RTC 产品的数量呈爆炸式增长,这在很大程度上是由于更便宜的高速网络访问和更强大的设备,但也归功于一个名为 WebRTC 的开放、免版税平台。WebRTC 正在从实现有用的体验发展成为在疫情期间让数十亿人继续工作和学习,并保持重要的人际联系的关键。WebRTC 的未来机遇和影响确实令人着迷。
Benjamin Treynor Sloss, Shylaja Nukala, Vivek Rau - 重要的指标
衡量您的站点可靠性指标,设定正确的目标,并努力准确地衡量这些指标。然后,您会发现您的服务运行得更好,停机时间更少,并且用户采用率更高。
Silvia Esparrachiari, Tanya Reilly, Ashleigh Rentz - 跟踪和控制微服务依赖关系
如果您曾经将钥匙锁在房屋或汽车内,您就会熟悉依赖关系循环。您无法在没有钥匙的情况下打开锁,但您无法在不打开锁的情况下拿到钥匙。有些循环很明显,但更复杂的依赖关系循环在导致中断之前可能很难找到。跟踪和控制依赖关系的策略对于维护可靠的系统是必要的。
Diptanu Gon Choudhury, Timothy Perrett - 为互联网规模服务设计集群调度器
希望构建调度系统的工程师应考虑他们使用的底层基础设施的所有故障模式,并考虑调度系统的运营商如何配置补救策略,同时帮助租户系统在租户系统所有者进行故障排除期间尽可能保持稳定。