前端性能
Service is too busy
为什么网站性能很重要?
- 40% 在等待页面加载 3 秒后放弃网站
- 80% 的人不会再回来
- 几乎一半的人会告诉别人他们的负面经历
- 亚马逊:每提高 100MS,收入增加 1%
网站速度与 SEO
网站速度对你的 SEO 工作很重要,因为更快的网站是:
- 更容易爬取
- 更容易访问
- 更有利于排名(尽管这是边际的)
- 最重要的是,更有可能留住访客!
Web 性能优化
Web 性能优化(Web Performance Optimization,WPO):
- 网站越快,用户的黏性越高:参与度
- 网站越快,用户忠诚度更高:留存率
- 网站越快,用户转化率越高。
延迟与带宽
延迟:分组从信息源发送到目的地所需的时间。
带宽:逻辑或物理通信路径最大的吞吐量。
延迟的构成要素
- 传播延迟:消息从发送端到接收端需要的时间,是信号传播距离和速度的函数
- 传输延迟:把消息中的所有比特转移到链路中需要的时间,是消息长度和链路速率的函数
- 处理延迟:处理分组首部、检查位错误及确定分组目标所需的时间
- 排队延迟:到来的分组排队等待处理的时间
Web 性能要点
- 延迟和带宽对 Web 性能的影响;
- 传输协议(TCP) 对 HTTP 的限制;
- HTTP 协议自身的功能和缺陷;
- Web 应用的发展趋势及性能需求;
- 浏览器局限性和优化思路。
不同层之间总是相互依赖, 但优化方式却有很多可能性。任何优化建议和最佳做法都不是一成不变的,涉及的每个要素都是动态发展的:
- 浏览器越来越快
- 用户上网条件不断改善
- Web 应用的功能和复杂度也与日俱增
浏览器页面渲染机制
导航
- DNS 查询
- TCP 握手:一旦获取到服务器 IP 地址,浏览器就会通过 TCP“三次握手”(en-US) 与服务器建立连接。TCP 的“三次握手”技术经常被称为“SYN-SYN-ACK”。
- TLS 协商:为了在 HTTPS 上建立安全连接,另一种握手是必须的。更确切的说是 TLS 协商,它决定了什么密码将会被用来加密通信,验证服务器,在进行真实的数据传输之前建立安全连接。在发送真正的请求内容之前还需要三次往返服务器。
响应
初始请求的响应包含所接收数据的第一个字节。Time to First Byte(TTFB)是用户通过点击链接进行请求与收到第一个 HTML 数据包之间的时间。
初始页面加载的 14KB 规则,第一个内容分块通常是 14KB 的数据。
TCP 慢启动 / 14KB 规则:
- 第一个响应数据包是 14KB 大小的。这是慢启动的一部分,慢启动是一种均衡网络连接速度的算法。慢启动逐渐增加发送数据的数量直到达到网络的最大带宽。
- 在 TCP 慢启动中,在收到初始包之后,服务器会将下一个数据包的大小加倍到大约 28KB。后续的数据包依次是前一个包大小的二倍直到达到预定的阈值,或者遇到拥塞。
解析
一旦浏览器收到数据的第一块,它就可以开始解析收到的信息。“解析”是浏览器将通过网络接收的数据转换为 DOM 和 CSSOM 的步骤,通过渲染器把 DOM 和 CSSOM 在屏幕上绘制成页面。
构建 DOM 树
第一步是处理 HTML 标记并构造 DOM 树。HTML 解析涉及到 tokenization 和树的构造。
当解析器发现非阻塞资源,例如一张图片,浏览器会请求这些资源并且继续解析。当遇到一个 CSS 文件时,解析也可以继续进行,但是对于 <script>
标签(特别是没有 async 或者 defer 属性的)会阻塞渲染并停止 HTML 的解析。尽管浏览器的预加载扫描器加速了这个过程,但过多的脚本仍然是一个重要的瓶颈。
预加载扫描器
浏览器构建 DOM 树时,这个过程占用了主线程。当这种情况发生时,预加载扫描仪将解析可用的内容并请求高优先级资源,如 CSS、JavaScript 和 web 字体。多亏了预加载扫描器,我们不必等到解析器找到对外部资源的引用来请求它。它将在后台检索资源,以便在主 HTML 解析器到达请求的资源时,它们可能已经在运行,或者已经被下载。预加载扫描仪提供的优化减少了阻塞。
1 |
|
构建 CSSOM 树
CSS 对象模型,简称为 CSSOM。
第二步是处理 CSS 并构建 CSSOM 树。CSS 对象模型和 DOM 是相似的。DOM 和 CSSOM 是两棵树。它们是独立的数据结构。浏览器将 CSS 规则转换为可以理解和使用的样式映射。浏览器遍历 CSS 中的每个规则集,根据 CSS 选择器创建具有父、子和兄弟关系的节点树。
其他过程
JavaScript 编译:当 CSS 被解析并创建 CSSOM 时,其他资源,包括 JavaScript 文件正在下载(借助预加载扫描器)。JavaScript 被解释、编译、解析和执行。脚本被解析为抽象语法树。一些浏览器引擎使用抽象语法树并将其传递到解释器中,输出在主线程上执行的字节码。这就是所谓的 JavaScript 编译。
构建辅助功能树:浏览器还构建辅助设备用于分析和解释内容的辅助功能(accessibility)树。无障碍对象模型(AOM)类似于 DOM 的语义版本。当 DOM 更新 时,浏览器会更新辅助功能树。辅助技术本身无法修改无障碍树。在构建 AOM 之前,屏幕阅读器(screen readers (en-US))无法访问内容。
渲染
渲染步骤包括样式、布局、绘制,在某些情况下还包括合成。在解析步骤中创建的 CSSOM 树和 DOM 树组合成一个 Render 树,然后用于计算每个可见元素的布局,然后将其绘制到屏幕上。在某些情况下,可以将内容提升到它们自己的层并进行合成,通过在 GPU 而不是 CPU 上绘制屏幕的一部分来提高性能,从而释放主线程。
Style
第三步是将 DOM 和 CSSOM 组合成一个 Render 树,计算样式树或渲染树从 DOM 树的根开始构建,遍历每个可见节点。
像 <head>
和它的子节点以及任何具有 display: none 样式的节点,例如 script { display: none; }
(在 user agent stylesheets 可以看到这个样式)这些标签将不会显示,也就是它们不会出现在 Render 树上。具有 visibility: hidden
的节点会出现在 Render 树上,因为它们会占用空间。由于我们没有给出任何指令来覆盖用户代理的默认值,因此上面 代码示例中的 script 节点将不会包含在 Render 树中。
每个可见节点都应用了其 CSSOM 规则。Render 树保存所有具有内容和计算样式的可见节点——将所有相关样式匹配到 DOM 树中的每个可见节点,并根据 CSS 级联确定每个节点的计算样式。
布局
第四步是在渲染树上运行布局以计算每个节点的几何体。布局(Layout)是确定呈现树中所有节点的宽度、高度和位置,以及确定页面上每个对象的大小和位置的过程。回流(reflow)是对页面的任何部分或整个文档的任何后续大小和位置的确定。
第一次确定节点的大小和位置称为布局随后对节点大小和位置的重新计算称为回流构建渲染树后,开始布局。渲染树标识显示哪些节点(即使不可见)及其计算样式,但不标识每个节点的尺寸或位置。为了确定每个对象的确切大小和位置,浏览器从渲染树的根开始遍历它。
绘制
最后一步是将各个节点绘制到屏幕上,第一次出现的节点称为 first meaningful paint (en-US)。在绘制或光栅化阶段,浏览器将在布局阶段计算的每个框转换为屏幕上的实际像素。
为了确保平滑滚动和动画,占据主线程的所有内容,包括计算样式,以及回流和绘制,必须让浏览器在 16.67 毫秒内完成。
为了确保重绘的速度比初始绘制的速度更快,屏幕上的绘图通常被分解成数层。如果发生这种情况,则需要进行合成。
绘制可以将布局树中的元素分解为多个层。将内容提升到 GPU 上的层(而不是 CPU 上的主线程)可以提高绘制和重新绘制性能。
分层确实可以提高性能,但是它以内存管理为代价,因此不应作为 web 性能优化策略的一部分过度使用。
当文档的各个部分以不同的层绘制,相互重叠时,必须进行合成,以确保它们以正确的顺序绘制到屏幕上,并正确显示内容。
交互
一旦主线程绘制页面完成,你会认为我们已经“准备好了”,但事实并非如此。如果加载包含 JavaScript(并且延迟到 onload 事件激发后执行),则主线程可能很忙,无法用于滚动、触摸和其他交互。
Time to Interactive (en-US)(TTI)是测量从第一个请求导致 DNS 查询和 SSL 连接到页面可交互时所用的时间——可交互是 First Contentful Paint (en-US) 之后的时间点,页面在 50ms 内响应用户的交互。如果主线程正在解析、编译和执行 JavaScript,则它不可用,因此无法及时(小于 50ms)响应用户交互。
每当浏览器遇到脚本标签时,DOM 构造就会暂停!整个 DOM 构建过程都将停止,直到脚本执行完成。JavaScript 可以同时修改 DOM 和 CSSOM
在默认情况下,每个脚本都是一个解析器阻断器!例外,async
关键渲染路径
周密的关键渲染路径(CRP)优化策略使浏览器能够通过确定优先加载的资源以及资源加载的顺序来尽可能快地加载页面。
优化 CRP:提升页面加载速度需要通过被加载资源的优先级、控制它们加载的顺序和减小这些资源的体积。性能提示包含:
- 通过异步、延迟加载或者消除非关键资源来减少关键资源的请求数量,
- 优化必须的请求数量和每个请求的文件体积,
- 通过区分关键资源的优先级来优化被加载关键资源的顺序,来缩短关键路径长度。
现代 Web 应用程序剖析
2022 年末,一个普通的 Web 应用由下列内容构成:
71 个请求,发送到 15 个主机,总下载量 2284 KB
- HTML:2 个请求,29 KB
- 图片:22 个请求,1001 KB
- JavaScript:22 个请求,515 KB
- CSS:7 个请求,75 KB
- Font:4 个请求,144 KB
- 其他资源:3 个请求,1 KB
- 视频:3 个请求,3472 KB
时间和用户感知
Delay | User Perception |
---|---|
0-100 ms | Instant 很快 |
100-300 ms | Small perceptible delay 有一点点慢 |
300-1000 ms | Machine is working 机器在工作呢 |
1,000+ ms | Likely mental context switch 先干点别的吧 |
10,000+ ms | Task is abandoned 不能用了 |
资源瀑布图
性能要素:计算、渲染、网络
web 程序的执行主要包括三个任务: 获取资源,页面布局和渲染,以及 JavaScript 执行。
- 更多带宽并不重要
- 延迟是性能瓶颈
Benchmark 工具
Apache Bench:apache 自带的一款功能强大的测试工具。
Siege 一款开源的压力测试工具,可以根据配置对一个 WEB 站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行。http://www.joedog.org/JoeDog/Siege
http_load (Excellent for latency tests)
- 程序非常小,解压后也不到 100K
- http_load 以并行复用的方式运行,用以测试 web 服务器的吞吐量与负载。
- http://www.acme.com/software/http_load/
阿里云性能测试(Performance Testing):一个 SaaS 性能测试平台,具有强大的分布式压测能力,可模拟海量用户真实的业务场景,
Tsung:一个开源的支持多协议的分布式压力测试工具
- 目前支持 HTTP 分布式压力测试、WebDAV 分布式压力测试、SOAP 分布式压力测试、PostgreSQL 分布式压力
- 测试、MySQL 分布式压力测试、LDAP 分布式压力测试、MQTT 分布式压力测试、Jabber/XMPP servers 分布式压力测试
JMeter:
- 作为一款广为流传的开源分布式压测产品,能自动生成图形报告。最初被设计用于 Web 应用测试,如今 JMeter 可以用于测试静态和动态资源,例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库、FTP 服务器等等,还能对服务器、网络或对象模拟巨大的负载,通过不同压力类别测试它们的强度和分析整体性能。另外,JMeter 能够对应用程序做功能测试和回归测试。
影响基准数据的因素
- 地理位置
- 网络问题
- 响应大小
- 代码处理
- 浏览器的行为
- Web 服务器配置
问题
人造测试不能发现所有性能瓶颈:
- 场景及页面选择:很难重复真实用户的导航模式;
- 浏览器缓存:用户缓存不同,性能差别很大;
- 中介设施:中间代理和缓存对性能影响很大;
- 硬件多样化:不同的 CPU、GPU 和内存比比皆是;
- 浏览器多样化:各种浏览器版本,有新有旧;
- 上网方式:真实连接的带宽和延迟可能不断变化。
性能监控指标
FP(全称“First Paint”,“首次绘制”):对于应用页面,首次出现视觉上不同于跳转之前内容的时间点,或者说是页面发生第一次绘制的时间点。
FCP(全称“First Contentful Paint”,“首次内容绘制”):指浏览器完成渲染 DOM 中第一部分内容(可能是文本、图像或其他任何元素)的时间点,此时用户应该在视觉上有直观的感受。注意:只有首次绘制文本、图片(包含背景图)、非白色的 canvas 或 SVG 时才被算作 FCP。
FP 与 FCP 这两个指标之间的主要区别是:
- FP 是当浏览器开始绘制内容到屏幕上的时候,只要在视觉上开始发生变化,无论是什么内容触发的视觉变化,在这一刻,这个时间点,叫做 FP。
- 相比之下,FCP 指的是浏览器首次绘制来自 DOM 的内容。例如:文本,图片,SVG,canvas 元素等,这个时间点叫 FCP。
- FP 和 FCP 可能是相同的时间,也可能是先 FP 后 FCP。
FMP(全称“First Meaningful Paint”,翻译为“首次有意义绘制”):指页面关键元素的渲染时间。
- 没有标准化定义,因为关键元素可以由开发者自行定义。
- FMP 本质上是通过一个算法来猜测某个时间点可能是 FMP,所以有时候不准。
首屏时间:进入页面之后,应用渲染完成整个手机屏幕(未滚动之前)内容的时间。
业界对于这个指标没有确切定论,比如是否包含屏幕内图片的渲染完成时间。
用户可交互时间:用户可以与应用进行交互的时间:
- 一般来说,是 DOMReady 的时间,因为通常会在这时绑定事件操作。
- 如果页面中涉及交互的脚步没有下载完成,那么当然没有到达所谓的用户可交互时间。
总下载时间:页面所有资源加载完成所需要的时间。
- 一般可以统计 window.onload 时间,这样可以统计出同步加载的资源全部加载完的耗时。
- 如果页面中存在较多的异步渲染,那么可以将异步渲染全部完成的时间做为总下载时间。
自定义指标:由于应用特点不同,可以根据需求自定义时间。
- 比如,一个类似 Instagram 的页面由图片瀑布流组成,那么可能非常关心屏幕中第一排图片渲染完成的时间。
导航计时 Navigation Timing 2
- DNS 查询耗时 = domainLookupEnd - domainLookupStart
- TCP 连接耗时 = connectEnd - connectStart
- 请求响应耗时 = responseEnd - requestStart
- 白屏时间 = domLoading - navigationStart
- 首屏时间 = loadEventStart - navigationStart
- DOM 树解析耗时 = domInteractive - domLoading
- Domready 时间 = domComplete - navigationStart
- onload 时间 = loadEventEnd - navigationStart
Performance API
Performance 接口可以获取到当前页面中与性能相关的信息。它是 High Resolution Time API 的一部分,同时也融合了 Performance Timeline API、Navigation Timing API、 User Timing API 和 Resource Timing API。
该类型的对象可以通过调用只读属性 Window.performance 来获得。
功能强大,但并不适用于所有场景。比如,如果在单页应用中改变 URL 但不刷新页面(单页应用的典型路由方案),那么使用 window.performance.timing 所获取的数据是不会更新的,还需要开发者重新设计统计方案。同时,window.performance.timing 可能无法满足一些自定义的数据。
分析工具
基于网页分析工具:
- Web PageTest, http://www.webpagetest.org/
- PingDom Tools
- GTmetrix, https://gtmetrix.com/
- Google PageSpeed, https://developers.google.com/speed/
- whatsmydns, https://www.whatsmydns.net
基于浏览器分析工具:
- Chrome 自带工具 F12
- Firefox 插件:YSlow(Yahoo 工具)
- Page Speed(google)
浏览器优化
可行的优化手段会因浏览器而异,但从核心优化策略来说,可以宽泛地分为两类:
基于文档的优化:熟悉网络协议,了解文档、CSS 和 JavaScript 解析管道,发现和优先安排关键网络资源,尽早分派请求并取得页面,使其尽快达到可交互的状态。主要方法是优先获取资源、提前解析等。
推测性优化:浏览器可以学习用户的导航模式,执行推测性优化,尝试预测用
户的下一次操作。然后,预先解析 DNS、预先连接可能的目标。
大多数浏览器利用的四种技术
好消息是,所有这些优化都由浏览器替我们自动完成,经常可以节省几百 ms 的网络延迟。既然如此,那理解这些优化背后的原理就至关重要了,这样才能利用浏览器的这些特性,提升应用性能。大多数浏览器都利用了如下四种技术。
资源预取和排定优先次序:文档、CSS 和 JavaScript 解析器可以与网络协议层沟 DNS 预解析:对可能的域名进行提前解析,避免将来 HTTP 请求时的 DNS 延迟。预解析可以通过学习导航历史、用户的鼠标悬停,或其他页面信号来触发。
TCP 预连接:DNS 解析之后,浏览器可以根据预测的 HTTP 请求,推测性地打开页面预渲染:某些浏览器可以让我们提示下一个可能的目标,从而在隐藏的标签页中推测优化
预解析特定的域名:
1 |
|
预取得页面后面要用到的关键性资源:
1 |
|
预取得将来导航要用的资源:
1 |
|
根据对用户下一个目标的预测,预渲染特定页面:
1 |
|
优化应用程序交付
最佳实践
两个准则:
- 消除或减少不必要的网络延迟
- 将需要传输的数据压缩至最少。
性能准则
减少 DNS 查找:每一次主机名解析都需要一次网络往返,从而增加请求的延迟时间,同时还会阻塞后续请求。
重用 TCP 连接:尽可能使用持久连接,以消除 TCP 握手和慢启动延迟。
减少 HTTP 重定向:HTTP 重定向极费时间,特别是不同域名之间的重定向,更加费时;这里面既有额外的 DNS 查询、TCP 握手,还有其他延迟。最佳的重定向次数为零。
使用 CDN(内容分发网络):把数据放到离用户地理位置更近的地方,可以显著减少每次 TCP 连接的网络延迟,增大吞吐量。这一条既适用于静态内容,也适用于动态内容。
去掉不必要的资源:任何请求都不如没有请求快。
其他准则
在客户端缓存资源:应该缓存应用资源,从而避免每次请求都发送相同的内容。
传输压缩过的内容:传输前应该压缩应用资源,把要传输的字节减至最少:确保对每种要传输的资源采用最好的压缩手段。
消除不必要的请求开销:减少请求的 HTTP 首部数据(比如 HTTP cookie),节省的时间相当于几次往返的延迟时间。
并行处理请求和响应:请求和响应的排队都会导致延迟,无论是客户端还是服务器端。这一点经常被忽视,但却会无谓地导致很长延迟。
针对协议版本采取优化措施:HTTP 1.x 支持有限的并行机制,要求打包资源、跨域分散资源,等等。相对而言,HTTP2.0 只要建立一个连接就能实现最优性能,同时无需针对 HTTP 1.x 的那些优化方法。
在客户端缓存资源
要说最快的网络请求,那就是不用发送请求就能获取资源。将之前下载过的数据缓存并维护好,就可以做到这一点。对于通过 HTTP 传输的资源,要保证首部包含适当的缓存字段:
- Cache-Control 首部用于指定缓存时间;
- Last-Modified 和 ETag 首部提供验证机制。
压缩传输的数据
利用本地缓存可以让客户端避免每次请求都重复取得数据。不过,还是有一些资源是必须取得的,比如原来的资源过期了,或者有新资源,再或者资源不能缓存。对于这些资源,应该保证传输的字节数最少。因此要保证对它们进行最有效的压缩。
HTML、CSS 和 JavaScript 等文本资源的大小经过 gzip 压缩平均可以减少 60%~80%。 而图片则需要仔细考量:
- 图片一般会占到一个网页需要传输的总字节数的一半;
- 通过去掉不必要的元数据可以把图片文件变小;
- 要调整大小就在服务器上调整,避免传输不必要的字节;
- 应该根据图像选择最优的图片格式;
- 尽可能使用有损压缩。
消除不必要的请求字节
HTTP 是一种无状态协议,也就是说服务器不必保存每次请求的客户端的信息。然而,很多应用又依赖于状态信息以实现会话管理、个性化、分析等功能。为了实现这些功能,HTTP State Management Mechanism(RFC 2965) 作为扩展,允许任何网站针对自身来源关联和更新 cookie 元数据:浏览器保存数据,而在随后发送给来源的每一个请求的 Cookie 首部中自动附加这些信息。
上述标准并未规定 cookie 最大不能超过多大,但实践中大多数浏览器都将其限制为 4 KB。与此同时,该标准还规定每个站点针对其来源可以有多个关联的 cookie。于是,一个来源的 cookie 就有可能多达几十 KB!不用说,这么多元数据随请求传递,必然会给应用带来明显的性能损失:
- 浏览器会在每个请求中自动附加关联的 cookie 数据;
- 在 HTTP 1.x 中,包括 cookie 在内的所有 HTTP 首部都会在不压缩的状态下传输;
- 在 HTTP 2.0 中,这些元数据经过压缩了,但开销依然不小;
- 最坏的情况下,过大的 HTTP cookie 会超过初始的 TCP 拥塞窗口,从而导致多余的网络往返。
并行处理请求和响应
要是想实现最佳性能,就要记住以下几点:
- 使用持久连接,从 HTTP 1.0 升级到 HTTP 1.1;
- 利用多个 HTTP 1.1 连接实现并行下载;
- 可能的情况下利用 HTTP 1.1 管道;
- 考虑升级到 HTTP 2.0 以提升性能;
- 确保服务器有足够的资源并行处理请求。
HTTP1.x 优化建议
针对 HTTP 1.x 的优化次序很重要:首先要配置服务器以最大限度地保证 TCP 和 TLS 的性能最优,然后再谨慎地选择和采用移动及经典的应用最佳实践,之后再度量,迭代。
采用了经典的应用优化措施和适当的性能度量手段,还要进一步评估是否有必要为应用采取特定于 HTTP 1.x 的优化措施(其实是权宜之计)。
- 利用 HTTP 管道:如果你的应用可以控制客户端和服务器这两端,那么使用管道可以显著减少网络延迟。
- 采用域名分区:如果你的应用性能受限于默认的每来源 6 个连接,可以考虑将资源分散到多个来源。
- 打包资源以减少 HTTP 请求:拼接和精灵图等技巧有助于降低协议开销,又能达成类似管道的性能提升。
- 嵌入小资源:考虑直接在父文档中嵌入小资源,从而减少请求数量。
管道缺乏支持,而其他优化手段又各有各的利弊。事实上,这些优化措施如果过于激进或使用不当,反倒会伤害性能。总之,要有务实的态度,通过度量来评估各种措施对性能的影响,在此基础上再迭代改进。 天底下就没有包治百病的灵丹妙药!!!
针对 HTTP 2.0 的优化
HTTP 2.0 的主要目标就是提升传输性能,实现客户端与服务器间较低的延迟和较高的吞吐量。显然,在 TCP 和 TLS 之上实现最佳性能,同时消除不必要的网络延迟, 从来没有如此重要过。
- 服务器的初始 cwnd 应该是 10 个分组; TCP 的拥塞控制主要原理依赖于一个拥塞窗口(cwnd)来控制,在之前我们还讨论过 TCP 还有一个对端通告的接收窗口(rwnd)用于流量控制。
- 服务器应该通过 ALPN(针对 SPDY 则为 NPN)协商支持 TLS;
- ALPN(Application Layer Protocol Negotiation,应用层协议协商),ALPN 是客户端发送所支持的 HTTP 协议列表,由服务端选择;
- NPN(Next Protocol Negotiation,下一代协议协商)是服务端发送所支持的 HTTP 协议列表,由客户端选择。
- 服务器应该支持 TLS 恢复以最小化握手延迟。
要通过 HTTP 2.0 获得最佳性能,特别是从每个来源仅用一个连接的角度说,的确需要各层协议的紧密配合。
接下来,或许有点意外,那就是采用移动及其他经典的最佳做法: 少发数据、削减请求,根据无线网络情况调整资源供给。不管使用什么版本的协议,减少传输的数据量和消除不必要的网络延迟,对任何应用都是最有效的优化手段。
最后,杜绝和忘记域名分区、文件拼接、图片精灵等不良的习惯,这些做法在 HTTP 2.0 之上完全没有必要。事实上,继续使用这些手段反而有害!可以利用 HTTP 2.0 内置的多路分发以及服务器推送等新功能。
去掉对 1.x 的优化
每个来源使用一个连接:HTTP 2.0 通过将一个 TCP 连接的吞吐量最大化来提升性能。事实上,在 HTTP 2.0 之下再使用多个连接(比如域名分区)反倒成了一种反模式,因为多个连接会抵 消新协议中首部压缩和请求优先级的效用。
去掉不必要的文件合并和图片拼接:打包资源的缺点很多,比如缓存失效、占用内存、延缓执行,以及增加应用复杂性。有了 HTTP 2.0,很多小资源都可以并行发送,导致打包资源的效率反而更低。
利用服务器推送:之前针对 HTTP 1.x 而嵌入的大多数资源,都可以而且应该通过服务器推送来交付。这样一来,客户端就可以分别缓存每个资源,并在页面间实现重用,而不必把它们放到每个页面里了。
要获得最佳性能,应该尽可能把所有资源都集中在一个域名之下。域名分区在 HTTP 2.0 之下属于反模式,对发挥协议的性能有害:分区是开始,之后影响会逐渐扩散。打包资源不会影响 HTTP 2.0 协议本身,但对缓存性能和执行速度有负面影响。
双协议应用策略
相同的应用代码,双协议部署:相同的应用代码可能通过 HTTP 1.x 也可能通过 HTTP 2.0 交付。可能任何一种协议之下都达不到最佳性能,但可以追求性能足够好。所谓足够好,需要通过针对每一种应用单独度量来保证。这种情况下,第一步可以先撤销域名分区以实现 HTTP 2.0 交付。然后,随着更多用户迁移到 HTTP 2.0,可以继续撤销资源打包并尽可能利用服务器推送。
分离应用代码,双协议部署:根据协议不同分别交付不同版本的应用。这样会增加运维的复杂性,但实践中对很多应用倒是十分可行。比如,一台负责完成连接的边界服务器可以根据协商后的协议版本,把客户端请求引导至适当的服务器。
动态 HTTP 1.x 和 HTTP 2.0 优化:某些自动化的 Web 优化框架,以及开源及商业产品,都可以在响应请求时动态重写交付的应用代码(包括连接、拼合、分区,等等)。此时,服务器也可以考虑协商的协议版本,并动态采用适当的优化策略。
HTTP 2.0,单协议部署:如果应用可以控制服务器和客户端,那没理由不只使用 HTTP 2.0。事实上,如果真有这种可能,那就应该专一使用 HTTP 2.0。
谷歌的 PageSpeed Optimization Libraries(PSOL) 提供了 40 多种“Web 优化过滤器”的开源实现,可以集成到任何服务器运行时,动态应用各种优化策略。
WebpageTest
Simple Testing
结果
指标
- 网页级指标 (Page-level Metrics):这些是为整个页面捕获并显示的顶级度量值。
- 整页加载时间 (Load Time):测量的时间是从初始化请求,到开始执行 window.onload 事件。
- 页面所有元素加载时间 (Fully Loaded):从初始化请求,到 Document Complete 后,2 秒内(中间几百毫秒轮询)没有网络活动的时间,但这 2 秒是不包括在测 量中的,所以会出现两个差值大于或小于 2 秒。
- 第一个字节加载时间 (First Byte):第一个字节时间(通常缩写为 TTFB)被测量为从初始化请求,到服务器响应的第一个字节,被浏览器接收的时间(不包括 DNS 查询、TCP 连接的时间)。
- 页面渲染时间 (Start Render):测量的时间是从初始化请求,到第一个内容被绘制到浏览器显示的时间。在瀑布图中有两个参数指标 Start Render 和 msFirstPaint。
- Start Render 是通过捕获页面加载的视频,并在浏览器第一次显示除空白页之外的其他内容时查看每个帧来衡量的。它只能在实验室测量,通常是最准确的测量。
- msFirstPaint(IE 专用属性)是由浏览器本身报告的一个测量,它认为绘制的第一个内容。通常是相当准确,但有时它报告的时候,浏览器只画一个空白屏幕。
- 首屏展现平均值 (Speed Index):表示页面呈现用户可见内容的速度(越低越好)。有关如何计算的更多信息,请参见:Speed Index。
- DOM 元素数量 (DOM Elements):在测试结束时测试页面上的 DOM 元素的计数。
- 请求级度量标准 (Request-level Metrics):这些是为每个请求捕获和显示的度量。
Filmstrip View
网飞网络性能案例研究
改善 Netflix.com 在桌面的交互时间
通过改进 Netflix.com 注册过程中所使用的 JavaScript 及预加载技术,开发团队能够为移动用户和桌面用户提供更好的用户体验,主要改进如下:
- 加载和交互时间减少了 50%(Netflix.com 桌面登录主页);
- 在从 React 和其他客户端库切换到普通的 JavaScript 之后,JavaScript 包大小减少了 200KB。服务器端仍然使用 React。
- HTML、CSS、JavaScript(React)预加载使后续页面的浏览交互时间减少了 30%。
减少 JavaScript 传输,缩短交互时间
Netflix 针对其登录主页的性能进行了优化:这个页面最初包含 300KB 的 JavaScript,其中一些是 React 和其他客户端代码(比如像 Lodash 这样的实用程序库),还有一些是补充 React 状态所需的上下文数据。
使用 Chrome 的开发工具和 Lighthouse 模拟在 3G 连接上加载登录主页,结果显示,登录主页需要 7 秒的加载时间,对于一个简单的登录页面来说太长了,因此需要研究改进的可能。通过一些性能审计,Netflix 发现他们客户端的 JS 开销很高。
登录主页是否真得需要 React?用 Vanilla JavaScript 替换!!!
优化
移植到原生 JavaScript 的组件列表:
- 基础交互(主页选项卡)
- 语言切换器
- “Cookie 横幅(Cookie banner)”(针对非美国用户)
- 客户端日志分析
- 性能度量和记录
- 广告归属检测引导代码(出于安全考虑,放在沙箱式 iFrame 中)
结果
尽管 React 最初占用的空间仅为 45KB,但将 React、几个库和相应的应用程序代码从客户端移除后,JavaScript 的总量减少了 200KB 以上,这使得 Netflix 在登录主页的交互时间减少了 50% 以上。
后续页面的 React 预加载
预加载:
- 通过浏览器内置的 API 和 XHR 预加载
- 交互时间减少了 30%
Robots.txt
“网络爬虫排除标准”(Robots Exclusion Protocol),网站通过 Robots 协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。
Robots 协议是国际互联网界通行的道德规范,基于以下原则建立:
- 搜索技术应服务于人类,同时尊重信息提供者的意愿,并维护其隐私权;
- 网站有义务保护其使用者的个人信息和隐私不被侵犯。
robots 是一个协议。robots.txt 文件是一个文本文件,放置在网站根目录下。
Robots.txt 例子
任何机器人都不应该访问任何以“/yoursite/temp/”开头的 URL,除了名为“IxeBot”的机器人:
1 |
|
总结
密切关注 JavaScript 的开销
Netflix 的折中方案是,使用 React 在服务器端渲染登录页面,但同时也为注册过程的其他部分预取 React 代码。这不仅优化了首次加载性能,还优化了注册过程其余部分的加载时间,因为它是单页应用,所以有更大的 JS 包需要下载。
补充:
- Netflix 考虑过 Preact,但是,对于一个交互性比较低的简单页面流,使用普通的 JavaScript 是一个更简单的选择。
- Netflix 尝试使用 Service Workers 进行静态资源缓存。当时,Safari 不支持这个 API(现在支持了),但他们现在又在探索这个 API。Netflix 的注册过程需要比客户体验更多的遗留浏览器支持。许多用户会在旧的浏览器上注册,但会在他们本地的移动应用程序或电视设备上观看 Netflix。
- Netflix 的登录页面极为动态。这是他们的注册过程中进行 A/B 测试最多的页面,机器学习模型用于根据位置、设备类型和许多其他因素定制消息和图像。支持近 200 个国家,每个派生页面都面对着不同的本地化、法律和价值信息挑战。