Web前端开发(10) HTTP、性能优化
2023-08-09 14:53:19 # NJU # Web前端开发

12-HTTP协议

1. Web 与 HTTP

  • Web页面由对象组成
  • 对象可以是HTML文件、JPEG图像、音频文件…
  • Web页面由基本html文件组成, 其中包含几个引用对象
  • 每个对象都可以通过URL寻址
  • 示例URL(统一资源定位器)
    • www.someschool.edu/someDept/pic.gif

HTTP 总览

  • HTTP: 超文本传输协议
  • Web的应用层协议
  • 客户机/服务器模型

    • 客户端: 请求、接收、显示Web对象的浏览器
    • 服务器: Web服务器发送对象以响应请求
  • 使用 TCP:

    • 客户端启动 TCP 连接(创建套接字)到服务器, 端口80
    • 服务器接受来自客户端的 TCP 连接
    • 浏览器(HTTP客户端)和 Web 服务器(HTTP服务器)之间交换 HTTP 消息(应用层协议消息)
    • TCP连接关闭

HTTP是无状态的

  • 服务器不维护关于过去客户端请求的信息

维护”状态”的协议非常复杂!

  • 必须保留过去的历史(状态)
  • 如果服务器/客户端崩溃, 它们的”状态”视图可能不一致, 必须进行协调

RFC 的定义

超文本传输协议 (HTTP) 是用于分布式、协作、超媒体信息系统的应用程序级协议。它是一种通用的、无状态的协议, 通过扩展其请求方法、错误代码和标头, 可用于超文本以外的许多任务, 例如名称服务器和分布式对象管理系统。HTTP 的一个特性是数据表示的类型化和协商, 允许独立于传输的数据构建系统

The Hypertext Transfer Protocol (HTTP) is an application-level protocol for distributed, collaborative, hypermedia information systems. It is a generic, stateless, protocol which can be used for many tasks beyond its use for hypertext, such as name servers and distributed object management systems, through extension of its request methods, error codes and headers . A feature of HTTP is the typing and negotiation of data representation, allowing systems to be built independently of the data being transferred.

https://www.w3.org/Protocols/rfc2616/rfc2616.html

HTTP 历史

  • HTTP Version 0.9
  • HTTP 1.0: RFC 1945
  • HTTP 1.1: RFC 2616
  • HTTPS (HTTP over TLS, HTTP over SSL, HTTP Secure)
  • 2009 Google 设计了基于TCP的SPDY
  • HTTP/2 (originally named HTTP/2.0)
    • Hypertext Transfer Protocol version 2 - RFC7540 /9113
    • HPACK - Header Compression for HTTP/2 - RFC7541
  • HTTP/3: RFC9114
    • QUIC(Quick UDP Internet Connections, 快速UDP网络连接)

HTTP 协议的演进

  • HTTP over QUIC
  • UDP

image-20230131205509308

HTTP/2

  • HTTP/2 是 HTTP 网络协议的一个重要版本。HTTP/2 的主要目标是通过启用完整的请求和响应多路复用来减少延迟, 通过有效压缩 HTTP 标头字段来最小化协议开销, 并增加对请求优先级和服务器推送的支持
  • HTTP/2 不会修改 HTTP 协议的语义。HTTP 1.1 中的所有核心概念(例如 HTTP 方法, 状态码, URI 和 headers)都得以保留。而是修改了 HTTP/2 数据在客户端和服务器之间的格式(帧)和传输方式, 这两者都管理整个过程, 并在新的框架层内隐藏了应用程序的复杂性。所以, 所有现有的应用程序都可以不经修改地交付

请求-响应

  • HTTP的结构很简单:
    • 客户端发送请求
    • 服务器返回一个响应
  • HTTP可以在单个TCP连接上支持多个请求-应答交换

HTTP 客户端和服务器端使用 TCP 套接字接口进行通信

image-20230131205812002

HTTP 事务延迟

image-20230131205937674

影响HTTP的常见的与tcp相关的延迟

  • TCP连接建立(三次握手)
  • TCP慢启动拥塞控制
  • Nagle的数据聚合算法(预防小分组)
  • TCP用于承载确认的延迟确认算法
  • TIME_WAIT延迟和端口耗尽

2. HTTP/1.x 的连接管理

image-20230131210040526

短连接

  • 每一个 HTTP 请求都由它自己独立的连接完成; 这意味着发起每一个 HTTP 请求之前都会有一次 TCP 握手, 而且是连续不断的
  • 这是 HTTP/1.0 的默认模型 (如果没有指定 Connection 协议头, 或者是值被设置为 close)。而在 HTTP/1.1 中, 只有当 Connection 被设置为 close 时才会用到这个模型
  • 短连接有两个比较大的问题: 创建新连接耗费的时间尤为明显, 另外 TCP 连接的性能只有在该连接被使用一段时间后 (热连接) 才能得到改善

长连接/持久连接

  • 一个长连接会保持一段时间, 重复用于发送一系列请求, 节省了新建 TCP 连接握手的时间, 还可以利用 TCP 的性能增强能力。当然这个连接也不会一直保留着: 连接在空闲一段时间后会被关闭(服务器可以使用 Keep-Alive 协议头来指定一个最小的连接保持时间)
  • 长连接也还是有缺点的; 就算是在空闲状态, 它还是会消耗服务器资源, 而且在重负载时, 还有可能遭受 DoS attacks 攻击。这种场景下, 可以使用非长连接, 即尽快关闭那些空闲的连接, 也能对性能有所提升
  • HTTP/1.0 里默认并不使用长连接。把 Connection 设置成 close 以外的其它参数都可以让其保持长连接, 通常会设置为 retry-after
  • 在 HTTP/1.1 里, 默认就是长连接的, 协议头都不用再去声明它 (但我们还是会把它加上, 万一某个时候因为某种原因要退回到 HTTP/1.0 呢)

HTTP 流水线

  • 默认情况下, HTTP 请求是按顺序发出的。下一个请求只有在当前请求收到应答过后才会被发出。由于会受到网络延迟和带宽的限制, 在下一个请求被发送到服务器之前, 可能需要等待很长时间
  • 流水线是在同一条长连接上发出连续的请求, 而不用等待应答返回。这样可以避免连接延迟。理论上讲, 性能还会因为两个 HTTP 请求有可能被打包到一个 TCP 消息包中而得到提升。就算 HTTP 请求不断的继续, 尺寸会增加, 但设置 TCP 的 MSS(Maximum Segment Size)选项, 仍然足够包含一系列简单的请求
  • 并不是所有类型的 HTTP 请求都能用到流水线: 只有 idempotent 方式, 比如 GET、HEAD、PUT 和 DELETE 能够被安全的重试: 如果有故障发生时, 流水线的内容要能被轻易的重试
  • 今天, 所有遵循 HTTP/1.1 的代理和服务器都应该支持流水线, 虽然实际情况中还是有很多限制: 一个很重要的原因是, 目前没有现代浏览器默认启用这个特性

域名分片

  • 如果服务器端想要更快速的响应网站或应用程序的应答, 它可以迫使客户端建立更多的连接。例如, 不要在同一个域名下获取所有资源, 假设有个域名是 www.example.com, 我们可以把它拆分成好几个域名: www1.example.com、www2.example.com、www3.example.com。所有这些域名都指向同一台服务器, 浏览器会同时为每个域名建立 6 条连接 (在我们这个例子中, 连接数会达到 18 条)。这一技术被称作域名分片
  • 备注: 除非你有紧急而迫切的需求, 不要使用这一过时的技术, 升级到 HTTP/2 就好了。

image-20230131211311309

HTTP 事务

image-20230131211512396

3. HTTP 消息

  • HTTP 消息是服务器和客户端之间交换数据的方式
  • HTTP 消息由采用 ASCII 编码的多行文本构成
  • 有两种类型的消息:
    • 请求(requests) — 由客户端发送用来触发一个服务器上的动作
    • 响应(responses) — 来自服务器的应答

image-20230131211618355

消息组成

  • HTTP 请求和响应具有相似的结构, 由以下部分组成︰
    • 一行起始行用于描述要执行的请求, 或者是对应的状态, 成功或失败。这个起始行总是单行的
    • 一个可选的 HTTP 头集合指明请求或描述消息正文
    • 一个空行指示所有关于请求的元数据已经发送完毕
    • 一个可选的包含请求相关数据的正文 (比如 HTML 表单内容), 或者响应相关的文档。正文的大小有起始行的 HTTP 头来指定
  • 起始行和 HTTP 消息中的 HTTP 头统称为请求头, 而其有效负载被称为消息正文

image-20230131211840418

HTTP 请求消息

HTTP请求消息:

  • ASCII
  • 行以CRLF”\r\n”结尾
  • 第一行叫做”请求行”

image-20230131212054367

请求方法

  • GET: 从服务器获取URL对应的资源
  • HEAD: 除了服务器响应中不能包含消息体, 该方法与GET一样。用于只需少数元信息的情况
  • POST: 被设计用来注解、修改URL所对应的资源
  • PUT: 被设计用来修改或创建资源。当URL对应的资源存在时, 则提交的作为新版本, 否则新建资源
  • DELETE: 被设计用来删除 URL 对应的资源
  • TRACE: 主要用来测试。服务器将最终接收到的请求本身发送回来, 作为客户端诊断依据
  • OPTIONS: 客户端查询服务器对与某 URL 允许的通信选项
  • CONNECT: 保留的方法名, 用于代理切换隧道
  • 支持扩展

方法示例

GET

image-20230131212557826

POST

image-20230131212729403

头域

  • 由主键/值对组成, 描述客户端或者服务器的属性、被传输的资源以及应该实现连接
  • 四种不同类型的头标:

    • 通用头标: 即可用于请求, 也可用于响应, 是作为一个整体而不是特定资源与事务相关联
    • 请求头标: 允许客户端传递关于自身的信息和希望的响应形式
    • 响应头标: 服务器和于传递自身信息的响应
    • 实体头标: 定义被传送资源的信息。即可用于请求, 也可用于响应
  • 实例

Accept: text/html
Host: www.nju.edu.cn
From: abc@nju.edu.cn
User-Agent: Mozilla/4.0
Referer: http://test.com/abc

HTTP 响应消息

  • ASCII 状态行

    • HTTP-Version Status-Code Reason-Phrase

      http协议版本、状态码(三位数字)、状态描述

  • 头域

  • 内容

image-20230131213125614

4. 状态码

请点击标题阅读文档

HTTP 响应状态码用来表明特定 HTTP 请求是否成功完成。响应被归为以下五大类:

  • 信息响应 (100–199)
  • 成功响应 (200–299)
  • 重定向消息 (300–399)
  • 客户端错误响应 (400–499)
  • 服务端错误响应 (500–599)

常用状态码

  • 200 OK
  • 301 Moved Permanently
  • 400 Bad Request
  • 401 Unauthorized
  • 403 forbidden
  • 404 Not Found
  • 500 Internal Server Error

301 - 永久重定向

什么情况下使用 301 重定向

  • 迁移到另外一个域名时, 通过 301 永久重定向将旧域名重定向至新域名, 挽回流量损失和SEO
  • 保持链接有效
    • 当重构 Web 站点的时候, 资源的 URL 会发生改变。你并不想因此而使旧链接失效, 因为它们会带来宝贵的用户(并且帮助优化你的 SEO), 所以需要建立从旧链接到新链接的重定向映射
  • 如果有多个闲置域名需要指向同一网站时, 通过 301 永久重定向可以实现
  • 打算实现网址规范化
  • 强制使用 HTTPS 协议。对于 HTTP 版本站点的请求会被重定向至采用了 HTTPS 协议的版本

image-20230131214331499

临时重定向

  • 有时候请求的资源无法从其标准地址访问, 但是却可以从另外的地方访问。在这种情况下可以使用临时重定向
  • 搜索引擎不会记录该新的、临时的链接。在创建、更新或者删除资源的时候, 临时重定向也可以用于显示临时性的进度页面
编码 含义 处理方法 典型应用场景
302 Found GET 方法不会发生变更, 其他方法有可能会变更为 GET 方法 由于不可预见的原因该页面暂不可用。在这种情况下, 搜索引擎不会更新它们的链接
303 See Other GET 方法不会发生变更, 其他方法会变更为 GET 方法(消息主体会丢失) 用于 PUTPOST 请求完成之后进行页面跳转来防止由于页面刷新导致的操作的重复触发
307 Temporary Redirect 方法和消息主体都不发生变化 由于不可预见的原因该页面暂不可用。在这种情况下, 搜索引擎不会更新它们的链接。当站点支持非 GET 方法的链接或操作的时候, 该状态码优于 302 状态码

特殊重定向

image-20230131214759453

Body

  • 请求/响应的最后一部分是它的 body
    • 不是所有的请求都有一个 body: 例如获取资源的请求, GET, HEAD, DELETE 和 OPTIONS, 通常它们不需要 body。有些请求将数据发送到服务器以便更新数据: 常见的的情况是 POST 请求(包含 HTML 表单数据)
    • 不是所有的响应都有 body: 具有状态码 (如 201 或 204) 的响应, 通常不会有 body
  • 任何信息
  • 需要Content-Length、Content-Type

HTTP/1.x 报文缺点

  • Header 不像 body, 它不会被压缩
  • 两个报文之间的 header 通常非常相似, 但它们仍然在连接中重复传输
  • 无法复用。当在同一个服务器打开几个连接时: TCP 热连接比冷连接更加有效

5. HTTP/2 - 为了更优异的表现

  • 在 2010 年到 2015 年, 谷歌通过实践了一个实验性的 SPDY 协议, 证明了一个在客户端和服务器端交换数据的另类方式。其收集了浏览器和服务器端的开发者的焦点问题。明确了响应数量的增加和解决复杂的数据传输, SPDY 成为了 HTTP/2 协议的基础
  • HTTP/2 在 HTTP/1.1 有几处基本的不同:
    • HTTP/2 是二进制协议而不是文本协议。不再可读, 也不可无障碍的手动创建, 改善的优化技术现在可被实施
    • 这是一个复用协议。并行的请求能在同一个链接中处理, 移除了 HTTP/1.x 中顺序和阻塞的约束
    • 压缩了 headers。因为 headers 在一系列请求中常常是相似的, 其移除了重复和传输重复数据的成本
    • 其允许服务器在客户端缓存中填充数据, 通过一个叫服务器推送的机制来提前请求

流、消息、帧

  • 流(Stream): 是在 HTTP/2 连接中在客户端和服务器之间交换的独立双向帧序列。流是连接中的一个虚拟信道, 可以承载双向消息传输。每个流有唯一整数标识符。为了防止两端流 ID 冲突, 客户端发起的流具有奇数ID, 服务器端发起的流具有偶数ID。能携带一个至多个消息
  • 消息(Message): 一个完整的请求或者响应, 由一个或多个帧组成
  • 帧(Frame): 在 HTTP/2 通信的最小单元。每个桢包括一个帧头, 里面有个很小标志, 来区别是属于哪个流

流的重要特征

  • 一个HTTP/2连接可以包含多个同时打开的流, 并且可以从多个流中交叉帧
  • 流可以单独建立和使用, 也可以由客户端或服务器共享
  • 流可以由任一端点关闭
  • 帧在流上发送的顺序非常重要。收件人按收到的顺序处理框架。特别是, HEADERS 和 DATA 帧的顺序在语义上很重要
  • 流用无符号的 31 位整数标识。流标识符由启动流的端点分配给流。客户端发起的流必须使用奇数流标识符; 那些由服务器发起的必须使用偶数流标识符

二进制格式

协议 格式
HTTP/1.x image-20230201112704210
HTTP/2 帧结构 image-20230201112713749

二进制帧层

image-20230201112801708

流:

image-20230201112923283

请求和响应多路复用

image-20230201113240975

image-20230201113306270

流量控制

  • 流量控制特定于连接。两种类型的流量控制都位于单跳的端点之间, 而不是整个端到端路径
  • 流量控制基于 WINDOW_UPDATE 帧。接收者宣告他们准备在一个流上以及整个连接上接收多少个字节
  • 流量控制是由接收器提供的全面控制方向。接收者可以选择为每个流和整个连接设置它想要的任何窗口大小。发送方必须遵守接收方施加的流量控制限制。客户端, 服务器和中介都独立地将其流量控制窗口作为接收者进行通告, 并遵守发送时由对等方设置的流量控制限制
  • 对于新流和整个连接, 流量控制窗口的初始值为 65,535 个八比特组
  • 帧类型决定流量控制是否适用于帧。在本文档中指定的帧中, 只有数据帧受流量控制; 所有其他帧类型不会占用通告的流量控制窗口中的空间。这确保了重要的控制框架不会被流量控制阻塞
  • 流量控制不能禁用
  • HTTP/2 仅定义 WINDOW_UPDATE 帧的格式和语义

优先级和依赖性

  • 每个流都包含一个优先级(也就是”权重”), 它被用来告诉对端哪个流更重要。当资源有限的时候, 服务器会根据优先级来选择应该先发送哪些流
  • 客户端可以通过在打开流的 HEADERS 帧中包含优先级信息来为新流分配优先级。在其他任何时候, PRIORITY 帧都可以用来改变流的优先级
  • 每个流可以被赋予对另一个流的显式依赖。包括依赖关系表示优先将资源分配给识别的流而不是依赖流。借助于 PRIORITY 帧, 客户端同样可以告知服务器当前的流依赖于其他哪个流。该功能让客户端能建立一个优先级”树”, 所有”子流”会依赖于”父流”的传输完成情况
  • 优先级和依赖关系可以在传输过程中被动态的改变。这样当用户滚动一个全是图片的页面的时候, 浏览器就能够指定哪个图片拥有更高的优先级。或者是在你切换标签页的时候, 浏览器可以提升新切换到页面所包含流的优先级

服务器推送

  • 这个功能通常被称作”缓存推送”。主要的思想是: 当一个客户端请求资源 X, 而服务器知道它很可能也需要资源 Z 的情况下, 服务器可以在客户端发送请求前, 主动将资源 Z 推送给客户端。这个功能帮助客户端将 Z 放进缓存以备将来之需
  • 服务器推送需要客户端显式的允许服务器提供该功能。但即使如此, 客户端依然能自主选择是否需要中断该推送的流。如果不需要的话, 客户端可以通过发送一个 RST_STREAM 帧来中止

image-20230201113851686

头压缩

  • http/1.x 的头带有大量信息, 而且每次都要重复发送。http/2 使用 encoder 来减少需要传输的 header 大小, 通讯双方各自缓存一份头部字段表, 既避免了重复 header 的传输, ⼜减小了需要传输的大小
  • 对于相同的数据, 不再通过每次请求和响应发送, 通信期间几乎不会改变通用键值对(用户代理、可接受的媒体类型, 等等), 只需发送一次
  • 事实上,如果请求中不包含首部(例如对同一资源的轮询请求), 那么, 首部开销就是零字节, 此时所有首部都自动使用之前请求发送的首部
  • 如果首部发生了变化, 则只需将变化的部分加入到 header 帧中, 改变的部分会加入到头部字段表中, 首部表在 http/2.0 的连接存续期内始终存在, 由客户端和服务器共同渐进地更新
  • 需要注意的是, http/2.0关注的是首部压缩, 而我们常用的 gzip 等是报文内容(body)的压缩, 二者不仅不冲突, 且能够一起达到更好的压缩效果
  • http/2 使用的是专门为首部压缩而设计的 HPACK 算法

image-20230201114416399

协商示例

GET /page HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: HTTP/2.0 // 协商升级协议到 2.0
HTTP2-Settings: (SETTINGS payload)

HTTP/1.1 200 OK
Content-length: 243
Content-type: text/html
(... HTTP 1.1 response ...)

(or)

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: HTTP/2.0
(... HTTP 2.0 response ...)
// 服务端启动 http2
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('localhost-privkey.pem'),
cert: fs.readFileSync('localhost-cert.pem')
});
server.on('error', (err) => console.error(err));
server.on('stream', (stream, headers) => {
// stream is a Duplex
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('<h1>Hello World</h1>');
});
server.listen(8443);

https://nodejs.org/api/http2.html#http2_server_side_example

6. 为何需要 HTTP/3

  • TCP 队头阻塞问题
  • TCP握手时长
  • 移动场景的网络切换成本
    • IP地址会发生变化, 而TCP协议是根据四元组来确定一个连接的, 需要重新建立连接

image-20230201114832016

HTTP/3

  • HTTP/3 基于 UDP 协议重新定义了连接, 在 QUIC 层实现了无序、并发字节流的传输, 解决了队头阻塞问题(包括基于 QPACK 解决了动态表的队头阻塞)
  • HTTP/3 重新定义了 TLS 协议加密 QUIC 头部的方式, 既提高了网络攻击成本, ⼜降低了建立连接的速度(仅需 1 个 RTT 就可以同时完成建链与密钥协商)
  • HTTP/3 将 Packet、QUIC Frame、HTTP3 Frame 分离, 实现了连接迁移功能, 降低了 5G 环境下高速移动设备的连接维护成本

image-20230201115255145

13-前端性能优化

1. 前端性能

为什么网站性能很重要?

  • 40% 在等待页面加载 3 秒后放弃网站
  • 80% 的人不会再回来
  • 几乎一半的人会告诉别人他们的负面经历
  • 亚马逊: 每提高 100MS, 收入增加 1%

网站速度与SEO

网站速度对你的SEO工作很重要, 因为更快的网站是:

  • 更容易爬取
  • 更容易访问
  • 更有利于排名(尽管这是边际的)
  • 最重要的是, 更有可能留住访客!

2. Web 性能优化 web performance optimization (WPO)

  • 网站越快, 用户的黏性越高; 参与度
  • 网站越快, 用户忠诚度更高; 留存率
  • 网站越快, 用户转化率越高

延迟与带宽

延迟: 分组从信息源发送到目的地所需的时间

带宽: 逻辑或物理通信路径最大的吞吐量

image-20230201154810793

延迟的构成要素

  • 传播延迟: 消息从发送端到接收端需要的时间, 是信号传播距离和速度的函数
  • 传输延迟: 把消息中的所有比特转移到链路中需要的时间, 是消息长度和链路速率的函数
  • 处理延迟: 处理分组首部、检查位错误及确定分组目标所需的时间
  • 排队延迟: 到来的分组排队等待处理的时间

Web性能要点

  • 延迟和带宽对Web性能的影响
  • 传输协议(TCP)对HTTP的限制
  • HTTP协议自身的功能和缺陷
  • Web应用的发展趋势及性能需求
  • 浏览器局限性和优化思路
  • 不同层之间总是相互依赖, 但优化方式却有很多可能性。任何优化建议和最佳做法都不是一成不变的, 涉及的每个要素都是动态发展的:
    • 浏览器越来越快
    • 用户上网条件不断改善
    • Web应用的功能和复杂度也与日俱增

3. 浏览器页面渲染机制

https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work

https://blog.logrocket.com/how-browser-rendering-works-behind-the-scenes-6782b0e8fb10

导航

  • DNS 查询
  • TCP 握手
    • 一旦获取到服务器 IP 地址, 浏览器就会通过 TCP”三次握手” (en-US)与服务器建立连接。TCP 的”三次握手”技术经常被称为”SYN-SYN-ACK”
  • TLS 协商
    • 为了在 HTTPS 上建立安全连接, 另一种握手是必须的。更确切的说是 TLS 协商, 它决定了什么密码将会被用来加密通信, 验证服务器, 在进行真实的数据传输之前建立安全连接。在发送真正的请求内容之前还需要三次往返服务器。

image-20230201155559322

响应

  • 初始请求的响应包含所接收数据的第一个字节。Time to First Byte(TTFB)是用户通过点击链接进行请求与收到第一个 HTML 数据包之间的时间
  • 初始页面加载的 14KB 规则, 第一个内容分块通常是 14KB 的数据
  • TCP 慢启动 / 14KB 规则
    • 第一个响应数据包是 14KB 大小的。这是慢启动的一部分, 慢启动是一种均衡网络连接速度的算法。慢启动逐渐增加发送数据的数量直到达到网络的最大带宽
    • TCP 慢启动 中, 在收到初始包之后, 服务器会将下一个数据包的大小加倍到大约 28KB。后续的数据包依次是前一个包大小的二倍直到达到预定的阈值, 或者遇到拥塞。

image-20230201160017470

解析

  • 一旦浏览器收到数据的第一块, 它就可以开始解析收到的信息。“解析”是浏览器将通过网络接收的数据转换为 DOMCSSOM 的步骤, 通过渲染器把 DOM 和 CSSOM 在屏幕上绘制成页面。

image-20230201160548507

构建 DOM 树

  • 第一步是处理 HTML 标记并构造 DOM 树。HTML 解析涉及到 tokenization 和树的构造

  • 当解析器发现非阻塞资源, 例如一张图片, 浏览器会请求这些资源并且继续解析。当遇到一个 CSS 文件时, 解析也可以继续进行, 但是对于 <script> 标签(特别是没有 async 或者 defer 属性的)会阻塞渲染并停止 HTML 的解析。尽管浏览器的预加载扫描器加速了这个过程, 但过多的脚本仍然是一个重要的瓶颈

image-20230201160935986

预加载扫描器

  • 浏览器构建 DOM 树时, 这个过程占用了主线程。当这种情况发生时, 预加载扫描仪将解析可用的内容并请求高优先级资源, 如 CSS、JavaScript 和 web 字体。多亏了预加载扫描器, 我们不必等到解析器找到对外部资源的引用来请求它。它将在后台检索资源, 以便在主 HTML 解析器到达请求的资源时, 它们可能已经在运行, 或者已经被下载。预加载扫描仪提供的优化减少了阻塞。
<link rel="stylesheet" src="styles.css"/>
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="image description"/>
<script src="anotherscript.js" async></script>

构建 CSSOM 树

  • CSS 对象模型, 简称为 CSSOM

  • 第二步是处理 CSS 并构建 CSSOM 树。CSS 对象模型和 DOM 是相似的。DOM 和 CSSOM 是两棵树。它们是独立的数据结构。浏览器将 CSS 规则转换为可以理解和使用的样式映射。浏览器遍历 CSS 中的每个规则集, 根据 CSS 选择器创建具有父、子和兄弟关系的节点树

image-20230201162043473

其他过程

JavaScript 编译

  • 当 CSS 被解析并创建 CSSOM 时, 其他资源, 包括 JavaScript 文件正在下载(借助预加载扫描器)。JavaScript 被解释、编译、解析和执行。脚本被解析为抽象语法树。一些浏览器引擎使用抽象语法树并将其传递到解释器中, 输出在主线程上执行的字节码。这就是所谓的 JavaScript 编译。

构建辅助功能树

  • 浏览器还构建辅助设备用于分析和解释内容的辅助功能(accessibility)树。无障碍对象模型(AOM)类似于 DOM 的语义版本。当 DOM 更新时, 浏览器会更新辅助功能树。辅助技术本身无法修改无障碍树。

  • 在构建 AOM 之前, 屏幕阅读器(screen readers (en-US))无法访问内容。

渲染

  • 渲染步骤包括样式、布局、绘制, 在某些情况下还包括合成。在解析步骤中创建的 CSSOM 树和 DOM 树组合成一个 Render 树, 然后用于计算每个可见元素的布局, 然后将其绘制到屏幕上。在某些情况下, 可以将内容提升到它们自己的层并进行合成, 通过在 GPU 而不是 CPU 上绘制屏幕的一部分来提高性能, 从而释放主线程

image-20230201162428370

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
    • 提升页面加载速度需要通过被加载资源的优先级、控制它们加载的顺序和减小这些资源的体积。
    • 性能提示包含
      • 通过异步、延迟加载或者消除非关键资源来减少关键资源的请求数量
      • 优化必须的请求数量和每个请求的文件体积
      • 通过区分关键资源的优先级来优化被加载关键资源的顺序, 来缩短关键路径长度

image-20230201163719221

4. 现代Web应用程序剖析

https://httparchive.org/reports/page-weight#bytesTotal

image-20230201164108418

性能要素:计算、渲染、网络

web程序的执行主要包括三个任务: 获取资源, 页面布局和渲染, 以及 JavaScript执行

  • 更多带宽并不重要
  • 延迟是性能瓶颈

Benchmark工具

image-20230201164817954

影响基准数据的因素

  • 地理位置、网络问题、响应大小、代码处理、浏览器的行为、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

image-20230201165615835

image-20230201165736167

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 可能无法满足一些自定义的数据。

分析工具

基于网页分析工具:

基于浏览器分析工具:

  • Chrome 自带工具 F12
  • Firefox 插件: YSlow(Yahoo 工具)
  • Page Speed(google)

浏览器优化

可行的优化手段会因浏览器而异, 但从核心优化策略来说, 可以宽泛地分为两类:

  • 基于文档的优化: 熟悉网络协议, 了解文档、CSS 和 JavaScript 解析管道, 发现和优先安排关键网络资源, 尽早分派请求并取得页面, 使其尽快达到可交互的状态。主要方法是优先获取资源、提前解析等
  • 推测性优化: 浏览器可以学习用户的导航模式, 执行推测性优化, 尝试预测用户的下一次操作。然后, 预先解析 DNS、预先连接可能的目标

大多数浏览器利用的四种技术

好消息是, 所有这些优化都由浏览器替我们自动完成, 经常可以节省几百 ms 的网络延迟。既然如此, 那理解这些优化背后的原理就至关重要了, 这样才能利用浏览器的这些特性, 提升应用性能。大多数浏览器都利用了如下四种技术

  • 资源预取和排定优先次序: 文档、CSS 和 JavaScript 解析器可以与网络协议层沟通, 声明每种资源的优先级: 初始渲染必需的阻塞资源具有最高优先级, ⽽低优先级的请求可能会被临时保存在队列中
  • DNS 预解析: 对可能的域名进行提前解析, 避免将来 HTTP 请求时的 DNS 延迟。预解析可以通过学习导航历史、用户的鼠标悬停, 或其他页面信号来触发
  • TCP 预连接: DNS 解析之后, 浏览器可以根据预测的 HTTP 请求, 推测性地打开 TCP 连接。 如果猜对的话, 则可以节省一次完整的往返(TCP 握⼿)时间
  • 页面预渲染: 某些浏览器可以让我们提示下一个可能的目标, 从而在隐藏的标签页中预先渲染整个页面。这样, 当用户真的触发导航时, 就能立即切换过来

推测优化

预解析特定的域名

<link rel="dns-prefetch" href="//hostname_to_resolve.com" />

预取得页面后面要用到的关键性资源

<link rel="subresource" href="/javascript/myapp.js" />

预取得将来导航要用的资源

<link rel="prefetch" href="/images/big.jpeg" />

根据对用户下一个目标的预测, 预渲染特定页面

<link rel="prerender" href="//example.org/next_page.html" />

优化应用程序交付

image-20230201170625047

5. 最佳实践

两个准则:

  1. 消除或减少不必要的网络延迟
  2. 将需要传输的数据压缩至最少

性能准则

  • 减少 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%。而图片则需要仔细考量:

  1. 图片一般会占到一个网页需要传输的总字节数的一半;
  2. 通过去掉不必要的元数据可以把图片文件变小;
  3. 要调整大小就在服务器上调整, 避免传输不必要的字节;
  4. 应该根据图像选择最优的图片格式;
  5. 尽可能使用有损压缩。

消除不必要的请求字节

HTTP 是一种无状态协议, 也就是说服务器不必保存每次请求的客户端的信息。然而, 很多应用又依赖于状态信息以实现会话管理、个性化、分析等功能。为了实现这些功能, HTTP State Management Mechanism(RFC 2965) 作为扩展, 允许任何网站针对自身来源关联和更新 cookie 元数据: 浏览器保存数据, 而在随后发送给来源的每一个请求的 Cookie 首部中自动附加这些信息。

上述标准并未规定 cookie 最大不能超过多大, 但实践中大多数浏览器都将其限制为 4 KB。与此同时, 该标准还规定每个站点针对其来源可以有多个关联的 cookie。于是, 一个来源的 cookie 就有可能多达几十 KB!不用说, 这么多元数据随请求传递, 必然会给应用带来明显的性能损失:

  1. 浏览器会在每个请求中自动附加关联的 cookie 数据;
  2. 在 HTTP 1.x 中, 包括 cookie 在内的所有 HTTP 首部都会在不压缩的状态下传输;
  3. 在 HTTP 2.0 中, 这些元数据经过压缩了, 但开销依然不小;
  4. 最坏的情况下, 过大的 HTTP cookie 会超过初始的 TCP 拥塞窗口, 从而导致多余的网络往返。

并行处理请求和响应

要是想实现最佳性能, 就要记住以下几点:

  1. 使用持久连接, 从 HTTP 1.0 升级到 HTTP 1.1;
  2. 利用多个 HTTP 1.1 连接实现并行下载;
  3. 可能的情况下利用 HTTP 1.1 管道;
  4. 考虑升级到 HTTP 2.0 以提升性能;
  5. 确保服务器有足够的资源并行处理请求。

HTTP/1.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 对网页进行测试

  • 网页级指标 (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): 这些是为每个请求捕获和显示的度量。

6. 网飞网络性能案例研究

改善 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% 以上。

image-20230201173307351

后续页面的 React 预加载

预加载:

  • 通过浏览器内置的 API 和 XHR 预加载
  • 交互时间减少了 30%

image-20230201173330145

7. Robots.txt

“网络爬虫排除标准”(Robots Exclusion Protocol), 网站通过 Robots 协议告诉搜索引擎哪些页面可以抓取, 哪些页面不能抓取。

Robots 协议是国际互联网界通行的道德规范, 基于以下原则建立:

  1. 搜索技术应服务于人类, 同时尊重信息提供者的意愿, 并维护其隐私权;
  2. 网站有义务保护其使用者的个人信息和隐私不被侵犯。

robots 是一个协议。robots.txt 文件是一个文本文件, 放置在网站根目录下。

Robots.txt 例子

任何机器人都不应该访问任何以”/yoursite/temp/“开头的 URL, 除了名为”IxeBot”的机器人:

User-agent: *
Disallow: /yoursite/temp/

User-agent: IxeBot
Disallow:

8. 总结

密切关注 JavaScript 的开销

Netflix 的折中方案是, 使用 React 在服务器端渲染登录页面, 但同时也为注册过程的其他部分预取 React 代码。这不仅优化了首次加载性能, 还优化了注册过程其余部分的加载时间, 因为它是单页应用, 所以有更大的 JS 包需要下载。

补充:

  • Netflix 考虑过 Preact, 但是, 对于一个交互性比较低的简单页面流, 使用普通的 JavaScript 是一个更简单的选择
  • Netflix 尝试使用 Service Workers 进行静态资源缓存。当时, Safari 不支持这个 API(现在支持了), 但他们现在又在探索这个 API。Netflix 的注册过程需要比客户体验更多的遗留浏览器支持。许多用户会在旧的浏览器上注册, 但会在他们本地的移动应用程序或电视设备上观看 Netflix
  • Netflix 的登录页面极为动态。这是他们的注册过程中进行 A/B 测试最多的页面, 机器学习模型用于根据位置、设备类型和许多其他因素定制消息和图像。支持近 200 个国家, 每个派生页面都面对着不同的本地化、法律和价值信息挑战