web 性能权威指南的读书笔记,加深一些理解和记忆。

# http2 的目的

  • 支持请求和响应的 多路复用 来减少网络延迟。

  • 通过压缩 http 首部字段将协议开销降至最低。

  • 支持请求 优先级

  • 服务端推送 能力。

# 增强性能的原因

  • # 二进制分帧层

协议定义了如何封装 http 的消息在客户端和服务端之间传输。http1.x 是以换行符作为纯为本的分隔符,而 http2.0 是将传输信息分割为更小的消息和帧,并且对它们进行二进制编码。所以只有客户端和服务端同时理解 http2.0 的两者才能进行通信。

说一下 流、消息、帧之间的关系。

  • :在已经建立的连接上的双向字节流。
  • 消息:完整的一系列的数据帧。
  • http2.0 中通信的最小单位,每个帧包含首部,至少会标识出当前的帧是属于哪个流。

串起来的一个流程大概就是: http2.0 协议连接之后,开始通信,其根本是在一个 tcp 的连接上完成的数据传输,在这个连接上,承载着任意数量的双向数据流。其中每一个数据流都是以消息【消息指代 http 的一些动作,请求、响应等】的方式发送的,而消息却是由一个或多个帧构成,其中这些帧都是以乱序发送的,在接受完数据之后,会根据帧的首部标识符来重新组装

分析一下二进制分帧中最常见的两种工作流:

  • # 发起新流

    建立完 http2.0 的连接之后,在发起数据之前,必须要创建一个新的流来承载消息,流中需要包含资源的优先级、 http 首部等信息。 http2.0 的规定,客户端和服务端都可以发起新流,只是两者的方式不同。

    • 客户端发送 HEADERS 帧来发起新流,其发起的流是具有奇数ID
    • 服务端发送 PUSH_PROMISE 帧来发起推流,其发起的流是具有偶数ID

    这样客户端和服务端每次发送流时候都会递增 ID ,两端的流并不会冲突。

  • # 交换应用数据

    创建完新流并且发送完 http 首部之后,接下来就是发送应用数据,数据不会被另行编码或者压缩,编码方式取决于应用或者服务器,在发送数据的时候,每帧的大小最多可以达到 2^16 - 1 字节,但是为了减少队首阻塞,http2.0 要求数据不能超过 2^14 - 1 ,如果数据超出,就需要分为多帧传输。

  • # 多向请求和响应

http1.x 中,如果客户端想发起多个并行的请求,那必须打开多个 tcp 的连接,但是其中多个响应的导致的最直接的问题就是排队,并且会导致队首阻塞,结果就是导致 tcp 的连接效率底下。

http2.0 中二进制分帧突破了这一些限制,其实现了多向请求和响应,客户端和服务端都可以将 http 消息分解为互不依赖的帧,然后乱序在连接上发送,最后重新组合。这一增强带来的是巨大的性能提升,因为多个请求并行且互不影响、互不干扰、只需要一个连接就可以并行发送多个请求和响应、消除了 http1.x 中不必要的延迟、队首阻塞以及多个 tcp 连接抵效的问题。在同等的情况下,http2.0 只需要一个 tcp 连接就可以实现并行等,看似减少了 tcp 的连接数,其实最直接的是减少了服务端的 cpu 和内存的消耗。

  • # 请求优先级

每一个在连接上传输的流都带有一个 31 比特的优先值,其中 0 最大,2^31 - 1 最小,服务端可以根据客户端资源请求的优先级来控制资源的分配cpu、内存、带宽),在数据准备好之后,将优先级最高的数据先发送给客户端。在客户端明确设置好各种资源的优先级之后,服务端应该按照优先级要求将资源依次返回,这样排队的问题基本上就会解决掉。

  • # 一个连接

http2.0 的所有传输都是在一个 tcp 的连接上完成,大多数的 http 连接都是突发并且短暂,但是因为 tcp慢启动以及流量拥塞窗口导致在经过一段时间之后才会很好的利用带宽,效率变高。而 http2.0 从头到尾的一个连接可以最大化和更有效的使用 tcp 的连接、减少网络延迟、提高吞吐量等。

  • # 流量控制

http2.0 连接建立之后,客户端和服务端会交换帧,其目的是设置双向的流量控制窗口的大小,从而控制流占用的资源,接收方针对特定的流动态调整窗口大小来限制或者提高传输速度。

  • # 服务端推送

顾名思义,服务端可以直接向客户端发送响应,在上边的流量控制中,交换帧之后,客户端会收到 PUSH_PROMISE 的帧,客户端可以视自己需求来处理,限定推送流的数量或者设置 0 禁止服务端推送。如果服务端已经预先知道了客户端即将需求请求什么资源,那么就可以直接推送给客户端资源提前加载,减少在需求的时候请求带来的延迟。并且服务端推送过来的数据是可以直接进行缓存的。但是服务端推送的缺点,其要遵守 请求-响应 的顺序,不能随意的发起推送,并且 PUSH_PROMISE 的帧必须在返回响应之前发送,避免出现竞态条件出现浪费资源的请求,也就是说要确保客户端的请求不正好是服务端推送的就行。

  • # 首部压缩

http1.x 每次请求都需要携带头部的元数据,再加上一个 cookie 信息,仅一次请求不携带别的数据,固定荷载就可能会接近上千字节,为了减少这个开销,http2.0 进行了首部元数据的压缩。具体的就是,每次的发送,对于相同的数据不再发送,http2.0 的首部在一个连接内持续存在,并且信息是由双端共同更新。如果有新的首部键值对,要么在尾部追加,要么就是替换之前的。所以下一次的请求发起之前,如果发现首部没变化,就不会再发送首部,这会极大减少请求携带数据的开销。举例: http1.x 的轮训,每次请求固定的头部开销可能回到 800k ,而 http2.0 之后,可能第一次发起请求的时候会发送头部数据,之后的所有请求都不会再发送,因为后边的首部都是自动使用之前请求发送的首部。

最后更新: 2/12/2023, 7:42:22 AM