sufuwang / demo

My study note about technology
0 stars 0 forks source link

HTTP2 #5

Open sufuwang opened 1 year ago

sufuwang commented 1 year ago

背景

HTTP1.1 存在以下问题

  1. 多个 TCP 链接: 建立 TCP 链接的成本较高
  2. 队头阻塞: 一个队列中的前一个请求完成前,后续请求必须等待
  3. Header 冗余
  4. 服务器不能推送

针对以上问题,HTTP2 协议 RFC-7540 诞生并发布于 2015 年,做了以下事情来解决这些问题

  1. 多路复用
  2. 二进制分帧
  3. Header 压缩
  4. ~Server Push~

Chrome 较早是支持 Server Push 的,但出于一些原因,Chrome 不再支持 Server Push,所以现在学习一下前三种方法

eg: HTTP1.1 & HTTP2 性能对比

sufuwang commented 1 year ago

减少 TCP 链接 & 减小队头阻塞时间

本地开启两个 Server ,逻辑如下所示

function fn(res) {
  if (req.url === "/") {
    res.end(readFileSync(__dirname + "/../src/index.html", "utf8"));
    return;
  }
  res.end("ok");
}

index.html 的逻辑如下所示

const handle = async () => await fetch('a')
Promise.all([...new Array(500)].map(() => handle()))

HTTP1.1 的 Network 如下图所示

image

HTTP2 的 Network 如下图所示

image

HTTP2 的 Stalled 和 Finish 时间、Connect 数都是小于 HTTP1.1 的,但是 HTTP2 并未真正解决队头阻塞的问题,在 HTTP2 中还是需要等待上一个请求完成再去执行下一个请求,在 TCP 协议下无法真正解决队头阻塞,即 TCP 数据包的顺序响应。有很多文章中说 HTTP2 解决了队头阻塞问题,这里的队头阻塞指的是 HTTP 应用曾协议层面的问题,即 HTTP1.1 必须顺序响应,前一个请求的延迟将同步体现到后续所有请求上,而 HTTP2 则使用分帧做到了乱序响应。下面来对比一下顺序响应带来的影响

本地开启两个 Server ,逻辑如下所示

function fn(res) {
  if (req.url === "/") {
    res.end(readFileSync(__dirname + "/../src/index.html", "utf8"));
    return;
  }
  if (req.url === "/delay") {
    setTimeout(() => {
      res.end("ok");
    }, 50);
    return;
  }
  res.end("ok");
}

index.html 的逻辑如下所示

const handle = async () => await fetch('a')
const delay = async () => await fetch('delay')
Promise.all([...new Array(500)].map((d, i) => i === 200 ? delay() : handle()))

HTTP1.1 的 Network 如下图所示

image

HTTP2 的 Network 如下图所示

image

对比可得,HTTP1.1 一个出现网络波动的请求,会对后续的所有请求造成影响,这并不是绝对的,当耗时超出阈值时,HTTP1.1 会新开一个 TCP 链接,如下图所示,图片已经按照 connect id 排序

image

结论

image

多路复用可以 解决 “多个 TCP 链接” 问题,二进制分帧可以 减缓 “队头阻塞” 问题,解决的是 HTTP 应用层协议的 “队头阻塞”,并没有解决 TCP 传输层协议的 “队头阻塞”

sufuwang commented 1 year ago

Chrome Timing Tab

Chrome 文档: Timing breakdown phases explained

image
名称 描述
Queueing 请求在队列中的等待时间。存在以下三种情况时,请求会进队列等待:有更高优先级的请求、HTTP1.x 下的 6 个TCP 链接都被占用、Chrome 正在分配磁盘缓存空间
Stalled Chrome 等待请求可以被发送的时间,包括等待代理协商的时间、等待可复用的 TCP 链接被先前需求释放的时间,不包括 DNS 查询时间、TCP 链接建立的时间
DNS Lookup DNS 查询耗时
Initial connection 通讯建立的耗时,包括 TCP 握手/重试耗时、SSL 握手耗时
Proxy negotiation 代理服务器协商耗时
Request sent 请求发送耗时
ServiceWorker Preparation Service Worker 启动耗时
Waiting (TTFB) TTFB = Time To First Byte ,请求发送后到收到响应首字节的等待时间
Content Download Chrome 接收从 Server / Worker 响应的耗时,耗时较长除了网络和 Server 的因素外,还有可能是 Chrome 无暇处理响应而导致耗时较长
~Receiving Push~ ~HTTP2 Server Push 耗时~
~Reading Push~ ~读取 Server Push 缓存的耗时~
sufuwang commented 1 year ago

二进制分帧

二进制分帧是 HTTP2 对 HTTP1.1 优化的核心。这小节来学习二进制分帧为什么可以解决 HTTP 应用层协议的 “队头阻塞” 问题

image image

HTTP2 中存在一个 TCP 链接,在这个链接中存在许多双向数据流,HTTP1.1 的一个消息被分成多个帧,通过这些双向数据流进行并发传输,在接收端进行重组,这些发生在二进制分帧层,处于应用层和传输层之间。TCP 协议的滑动窗口是一个队列,不能并发处理消息,所以这些并不能真正解决 “队头阻塞”

sufuwang commented 1 year ago

Header 压缩

HTTP2 使用 HPACK 算法完成 Header 压缩,大概原理如下

  1. 前后端具有同一份静态表和动态表,用来维护当前 Header
  2. 前端请求时携带的 Header ,使用两个表进行编码,并带有一些特殊指令
  3. 后端收到请求时,使用两个表进行解码,然后使用两个表拼出完整 Header ,并根据特殊指令判断是否更新动态表

HTTP1.1 的 Header

image

HTTP2 的 Header