Web 性能介绍和优化

发布于 2024-05-07 05:53:37 字数 14390 浏览 28 评论 0

基本概念

延迟的构成

  • 传播延迟:发送端到接收端的时间,受光速不可超越理论的限制。与距离有关。
  • 传输延迟:由传输链路的速率决定,10M 文件在 100Mbit/s 链路上只需 0.1s。与距离无关。
  • 处理延迟:路由器处理分组首部,检查,确定出站路由的时间。
  • 排队延迟:分组到达速度超过路由器的处理能力,那就先进入缓冲区排队等候。

上述时间之和=客户端到服务器的总延迟时间。可以使用 traceroute 命令测量延迟。

网络核心带宽

  • 一条光纤连接的总带宽,等于每个信道的数据传输速率*可复用的信道数。
  • 一个信道耦合 400+光线,最大容量可达 171Gbit/s,一条光纤总带宽可达 70Tbit/s。

目标:高带宽和低延迟

  • 高带宽:可以多铺设光缆。
  • 低延迟:time=distance/v
    • 提升 v 的角度:降低光纤中光信号的折射率,让光信号传输更快。然而,光纤折射率受限于材料,很容易进入瓶颈。
    • 降低距离的角度:把距离缩短。

TCP 构成

TCP 的作用:在不可靠的传输信道上,提供可靠的抽象层。隐藏了丢包重发、按序发送、拥塞控制、数据完整性检验等复杂细节。

三次握手

  • SYN:发送端随机序号 x,发送到接收端。
  • SYN ACK:接收端将 x+1,选择随机序列号 y,返回。
  • ACK:发送端将 y+1 与 x+1,并发送 ACK 分组。

示意图 https://wiki.wireshark.org/TCP_3_way_handshaking

三次握手的延迟使得创建一个新 TCP 连接要付出很大代价。决定了提高 TCP 应用性能的关键在于重用连接。

TCP 快速打开。

流量控制

为实现流量控制,TCP 连接的每一方都要通告自己的接收窗口(rwnd),其中包含能够保存数据的缓冲区空间大小
信息。

流量控制是一种预防发送端向接收端发送数据的机制。

  • 浏览网页:客户端向服务器下载数据,客户端窗口成为瓶颈。
  • 上传文件:客户端向服务器传送大量数据,服务器接收窗口成为瓶颈。

其中一端如果跟不上数据传输,可以向另一端通告一个较小的窗口。

Linux 设置窗口缩放:

sysctl net.ipv4.tcp_window_scaling
sysctl -w net.ipv4.tcp_window_scaling=1

慢启动

发送端和接收端在连接建立之初,不知道可用带宽,需要一个估算机制来根据网络中不断变化的条件而动态改变速度。

  • 拥塞窗口大小(cwnd)

根据交换数据来估算客户端与服务器之间的可用带宽是唯一的方法,这是慢启动算法的设计思路。
首先,服务器通过 TCP 连接初始化一个新的拥塞窗口(cwnd)变量,
将其值设置为一个系统设定的保守值(在 Linux 中就是 initcwnd)。

  • 慢启动

在分组被确认后增大窗口大小,慢慢地启动。最初,cwnd 的值只有 1 个 TCP 段。
1999 年 4 月,RFC 2581 将其增加到了 4 个 TCP 段。
2013 年 4 月,RFC 6928 再次将其提高到 10 个 TCP 段。

4 -> 8 -> 16 -> 32 -> 64 -> 开始丢包 -> 32 -> 缓慢增加 cwnd。

从一个相对较小的拥塞窗口开始,每次往返令其翻倍。
而达到某个目标吞吐量所需的时间,就是客户端与服务器之间的往返时间和初始拥塞窗口大小的函数。

假设:

  • 客户端和服务端接收窗口为 65535 字节(64KB)。
  • 初始拥塞窗口:4 段。
  • 往返时间为 56ms。

为减少增长到拥塞窗口的时间,可以

  • 减少客户端与服务器的往返时间 => 利用 CDN 加速。
  • 将初始拥塞窗口大小 cwnd 增加到 10 段。Linux 上,IW10 是 2.6.39+的特性。

  • 禁用慢启动重启(SSR)

慢启动重启的作用:在连接空闲一定时间后重置连接的拥塞窗口。

sysctl net.ipv4.tcp_slow_start_after_idle
sysctl -w net.ipv4.tcp_slow_start_after_idle=0

拥塞预防

慢启动以保守的窗口初始化连接,随后每次往返成倍提高传输的数据量,直到超过接收端的流量控制窗口,即系统
配置的拥塞阈值(ssthresh)窗口,或者有分组丢失为止,此时拥塞预防算法介入。

TCP 比例降速

  • 最初,TCP 使用 AIMD(Multiplicative Decrease and Additive Increase,倍减加增)
    算法,发生丢包时,先将拥塞窗口减半,然后每次往返再缓慢地给窗口增加一
    个固定的值。不过,很多时候 AIMD 算法太过保守。

  • PRR(Proportional Rate Reduction,比例降速)是 RFC 6937 规定的一个新算法,
    目标是改进丢包后的恢复速度。

无需按序交付数据或能够处理分组丢失的应用程序,以及对延迟或抖动要求很高的
应用程序,最好选择 UDP 等协议。

TCP 优化建议

TCP 核心原理。

  • TCP 三次握手增加了整整一次往返时间;
  • TCP 慢启动被应用到每个新连接;
  • TCP 流量及拥塞控制会影响所有连接的吞吐量;
  • TCP 的吞吐量由当前拥塞窗口大小控制。
  • 瓶颈一般是延迟,而不是带宽。

服务器配置调优

  • 增大 TCP 初始拥塞窗口
  • 禁用慢启动重启
  • 窗口缩放
  • TCP 快速打开

应用程序行为调优

  • 减少传输的数据
  • 压缩必须传输的数据
  • 利用 CDN 加速
  • 重用 TCP 连接

UDP 构成

无协议服务

  • 不保证消息交付
    • 不确认,不重传,无超时。
  • 不保证交付顺序
    • 不设置包序号,不重排,不会发生队首阻塞。
  • 不跟踪连接状态
    • 不必建立连接或重启状态机。
  • 不需要拥塞控制
    • 不内置客户端或网络反馈机制。

UDP 优化建议

  • 应用程序必须容忍各种因特网路径条件;
  • 应用程序应该控制传输速度;
  • 应用程序应该对所有流量进行拥塞控制;
  • 应用程序应该使用与 TCP 相近的带宽

TLS

TLS 协议的目标是为在它之上运行的应用提供三个基本服务:加密、身份验证和数
据完整性。

TLS 握手

  • 112 ms:假设两端经过协商确定了共同的版本和加密套件,客户端也把自己的证书提供给了服务器。
    然后,客户端会生成一个新的对称密钥,用服务器的公钥来加密,加密后发送给服务器,告诉服务器可以开始加密通信了。
    到目前为止,除了用服务器公钥加密的新对称密钥之外,所有数据都以明文形式发送。
  • 140 ms:服务器解密出客户端发来的对称密钥,通过验证消息的 MAC 检
    测消息完整性,再返回给客户端一个加密的“Finished”消息。
  • 168 ms:客户端用它之前生成的对称密钥解密这条消息,验证 MAC,如果一切
    顺利,则建立信道并开始发送应用数据。

每一个 TLS 连接在 TCP 握手基础上最多还需要两次额外的往返。

浏览器的优化建议

  • 基于文档的优化

熟悉网络协议,了解文档、CSS 和 JavaScript 解析管道,发现和优先安排关键网
络资源,尽早分派请求并取得页面,使其尽快达到可交互的状态。主要方法是优
先获取资源、提前解析等。

  • 推测性优化

浏览器可以学习用户的导航模式,执行推测性优化,尝试预测用户的下一次操作。然后,预先解析 DNS、预先连接可能的目标。


大多数浏览器都利用了如下四种技术。

  • 资源预取和排定优先次序

文档、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"> ➍
➊ 预解析特定的域名
➋ 预取得页面后面要用到的关键性资源
➌ 预取得将来导航要用的资源
➍ 根据对用户下一个目标的预测,预渲染特定页面

HTTP 发展史

  • HTTP 0.9:只有一行的 ASCII 请求,用于取得一个超文本文档。
  • HTTP 1.0:增加了请求和响应首部,双方能够交换有关请求和响应的元信息。
  • HTTP 1.1:服务器和客户端可以扩展首部,始终以纯文本形式发送。

HTTP 1.x

  • 减少 DNS 查询
  • 减少 HTTP 请求
  • 使用 CDN
  • Gzip 资源
  • 避免 HTTP 重定向
  • 添加 Expires 首部并配置 ETag 及 Last-Modified 首部
  • 域名分区,突破每主机并行连接数的限制,代价是额外 DNS 查询

连接与拼合

  • 连接:把多个 JavaScript 或 CSS 文件组合为一个文件。
  • 拼合:把多张图片组合为一个更大的复合的图片。

降低网络请求次数,代价是缓存很难利用,资源也不容易更新。

HTTP 2.0

HTTP 2.0 的目的:

  • 请求与响应的多路复用来减少延迟
  • 压缩 HTTP 首部字段将协议开销降低
  • 增加对请求优先级和服务器端推送的支持
  • 其他协议层面的辅助实现,比如新的流量控制、错误处理和更新机制
  • 新的二进制分帧数据层

二进制分帧层

HTTP 1.x 以换行符作为纯文本的分隔符,而 HTTP 2.0 将所有传输的信息分割为更小的消息和帧,采用二进制编码。
不兼容之前的 HTTP 1.x,所以称为 2.0。

二进制分帧

  • 16 位的长度前缀意味着一帧大约可以携带 64 KB 数据,不包括 8 字节首部。
  • 8 位的类型字段决定如何解释帧其余部分的内容。
  • 8 位的标志字段允许不同的帧类型定义特定于帧的消息标志。
  • 1 位的保留字段始终置为 0。
  • 31 位的流标识符唯一标识 HTTP 2.0 的流。

流、消息和帧

  • 所有通信都在一个 TCP 连接上完成。
  • 流是连接中的一个虚拟信道,可以承载双向的消息;每个流都有一个唯一的整数标识符(1、2…N)。
  • 消息是指逻辑上的 HTTP 消息,比如请求、响应等,由一或多个帧组成。
  • 帧是最小的通信单位,承载着特定类型的数据,如 HTTP 首部、负荷,等等。

多向请求和响应

图 12-3 中包含了同一个连接上多个传输中的数据流:客户端正在向服务器传输一个
DATA 帧(stream 5),与此同时,服务器正向客户端乱序发送 stream 1 和 stream 3
的一系列帧。一个连接上有 3 个请求/ 响应并行交换!

  • 可以并行交错地发送请求,请求之间互不影响;
  • 可以并行交错地发送响应,响应之间互不干扰;
  • 只使用一个连接即可并行发送多个请求和响应;
  • 消除不必要的延迟,从而减少页面加载的时间;
  • 不必再为绕过 HTTP 1.x 限制而多做很多工作;

请求优先级

  • 0 表示最高优先级;
  • 2^31-1 表示最低优先级。

首部差异化编码

经典性能优化最佳实践

  • 减少 DNS 查找

每一次主机名解析都需要一次网络往返,从而增加请求的延迟时间,同时还会阻
塞后续请求。

  • 重用 TCP 连接

尽可能使用持久连接,以消除 TCP 握手和慢启动延迟。

  • 减少 HTTP 重定向

HTTP 重定向极费时间,特别是不同域名之间的重定向,更加费时;这里面既有
额外的 DNS 查询、TCP 握手,还有其他延迟。最佳的重定向次数为零。

  • 使用 CDN(内容分发网络)

把数据放到离用户地理位置更近的地方,可以显著减少每次 TCP 连接的网络延
迟,增大吞吐量。这一条既适用于静态内容,也适用于动态内容。

  • 去掉不必要的资源

任何请求都不如没有请求快。

  • 在客户端缓存资源

应该缓存应用资源,从而避免每次请求都发送相同的内容。

  • 传输压缩过的内容

传输前应该压缩应用资源,把要传输的字节减至最少:确保对每种要传输的资源
采用最好的压缩手段。

  • 消除不必要的请求开销

减少请求的 HTTP 首部数据(比如 HTTP cookie),节省的时间相当于几次往返
的延迟时间。

  • 并行处理请求和响应

请求和响应的排队都会导致延迟,无论是客户端还是服务器端。

  • 针对协议版本采取优化

HTTP 1.x 支持有限的并行机制,要求打包资源、跨域分散资源,等等。
HTTP 2.0 只要建立一个连接就能实现最优性能,同时无需针对 HTTP 1.x 的
那些优化方法。

在客户端缓存资源

  • Cache-Control 首部用于指定缓存时间;
  • Last-Modified 和 ETag 首部提供验证机制。
  • 应同时指定缓存时间和验证方法

消除不必要的请求字节

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

针对 HTTP 1.x 的优化建议

  • 利用 HTTP 管道

如果你的应用可以控制客户端和服务器这两端,那么使用管道可以显著减少网络延迟。

  • 采用域名分区

如果你的应用性能受限于默认的每来源 6 个连接,可以考虑将资源分散到多个来源。

  • 打包资源以减少 HTTP 请求

拼接和精灵图等技巧有助于降低协议开销,又能达成类似管道的性能提升。

  • 嵌入小资源

考虑直接在父文档中嵌入小资源,从而减少请求数量。

浏览器网络概述

高层浏览器网络 API、协议和服务

Socket Pool

XHR、SSE、Websocket

XMLHttpRequest

XHR 下载数据

  • ArrayBuffer 固定长度的二进制数据缓冲区。
  • Blob 二进制大对象或不可变数据。
  • Document 解析后得到的 HTML 或 XML 文档。
  • JSON 表示简单数据结构的 JavaScript 对象。
  • Text 简单的文本字符

下载 blob 示例

var xhr = new XMLHttpRequest();
xhr.open('GET', '/images/photo.webp');
xhr.responseType = 'blob'; ➊
xhr.onload = function() {
  if (this.status == 200) {
    var img = document.createElement('img');
    img.src = window.URL.createObjectURL(this.response); ➋
    img.onload = function() {
      window.URL.revokeObjectURL(this.src); // ➌ 图片加载完毕后释放对象
    }
    document.body.appendChild(img);
  }
};
xhr.send();

监控下载和上传进度

事件类型说明触发次数
loadstart传输已开始一次
progress正在传输零或多次
error传输出错零或多次
abort传输终止零或多次
load传输成功零或多次
loadend传输完成一次

XHR 轮询

// 每个 XHR 请求都是一次独立的 HTTP 请求
function checkUpdates(url) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = function() { ... }; 
  xhr.send();
}
setInterval("checkUpdates('/updates'), 60000");

XHR 长轮询

function checkUpdates(url) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = function() { ➊
    ...
    checkUpdates('/updates'); ➋
  };
  xhr.send();
}
checkUpdates('/updates'); ➌
//➊ 处理更新并打开新的长轮询 XHR
//➋ 发送长轮询请求并等待下次更新(如此不停循环)
//➌ 发送第一次长轮询 XHR 请求

XHR 的缺点

  • XHR 标准从未考虑流式数据处理的场景,对它的支持也有限。
    造成了通过 XHR 进行流式数据传输既低效,又不方便。
  • 定时轮询会导致高开销及更新延迟。
    长轮询的延迟低,但每次更新仍然有开销,因为每次更新都需要一次 HTTP 请求。

Server-Sent Events(SSE)

EventSource 接口通过一个简单的浏览器 API 隐藏了所有的底层细节,包括建立连接
和解析消息。

var source = new EventSource("/path/to/stream-url"); ➊
source.onopen = function () { ... }; ➋
source.onerror = function () { ... }; ➌
source.addEventListener("foo", function (event) { ➍
  processFoo(event.data);
});
source.onmessage = function (event) { ➎
  log_message(event.id, event.data);
  if (event.id== "CLOSE") {
    source.close(); ➏
  }
}

Event Stream 协议

Websocket

SSE 作为一种服务器向客户端传送文本数据的机制。如果你想传输二进制数据,WebSocket 才是更
合适的选择。

  • XHR 是专门为“事务型”请求/响应通信而优化的:客户端向服务器发送完整
    的、格式良好的 HTTP 请求,服务器返回完整的响应。这里不支持请求流,在
    Streams API 可用之前,没有可靠的跨浏览器响应流 API。
  • SSE 可以实现服务器到客户端的高效、低延迟的文本数据流:客户端发起 SSE 连
    接,服务器使用事件源协议将更新流式发送给客户端。客户端在初次握手后,不
    能向服务器发送任何数据。
  • WebSocket 是唯一一个能通过同一个 TCP 连接实现双向通信的机制(图 17-2),客户端和服务器随时可以交换数据。
    WebSocket 在两个方向上都能保证文本和二进制应用数据的低延迟交付。

WebRTC

WebRTC 的音频和视频引擎

MediaStream

音频(Opus)与视频(VP8)比特率

当前的 WebRTC 实现使用 Opus 和 VP8 编解码器:

  • Opus 编解码器用于音频,支持固定和可变的比特率编码,适合的带宽范围为
    6~510 Kbit/s。这个编解码器可以无缝切换,以适应不同的带宽。
  • VP8 编解码器用于视频编码,要求带宽为 100~2000+ Kbit/s,比特率取决于
    流的品质:
    • 720p,30 FPS:1.0~2.0 Mbit/s
    • 360p,30 FPS:0.5~1.0 Mbit/s
    • 180p,30 FPS:0.1~0.5 Mbit/s

实时网络传输

UDP 首选。

  • Chrome 为检查 WebRTC 连接提供了一个工具。chrome://webrtc-internals
  • 参考 simpleWebRTC 的文档( http://simplewebrtc.com/ )

通过 SRTP 和 SRTCP 交付媒体

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

凡尘雨

暂无简介

0 文章
0 评论
909 人气
更多

推荐作者

小瓶盖

文章 0 评论 0

wxsp_Ukbq8xGR

文章 0 评论 0

1638627670

文章 0 评论 0

仅一夜美梦

文章 0 评论 0

夜访吸血鬼

文章 0 评论 0

近卫軍团

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文