Tip:面试
2015 年 HTTP/2 标准发表后,大多数主流浏览器也于当年年末支持该标准。此后,凭借着多路复用、头部压缩、服务器推送等优点,HTTP/2 获得了愈来愈多知名互联网公司的青睐。就在你们刚刚为了解了 HTTP/2 新特性而舒口气儿的时候,HTTP/3 却又紧锣密鼓地准备着了。今天就跟你们聊一聊这第三代 HTTP 技术。算法
在介绍 HTTP 以前,咱们先简单看下 HTTP 的历史,了解下 HTTP/3 出现的背景。 浏览器
TCP 一直是传输层中举足轻重的协议,而 UDP 则默默无闻,在面试中问到 TCP 和 UDP 的区别时,有关 UDP 的回答经常寥寥几语,长期以来 UDP 给人的印象就是一个很快但不可靠的传输层协议。但有时候从另外一个角度看,缺点可能也是优势。QUIC(Quick UDP Internet Connections,快速 UDP 网络链接) 基于 UDP,正是看中了 UDP 的速度与效率。同时 QUIC 也整合了 TCP、TLS 和 HTTP/2 的优势,并加以优化。用一张图能够清晰地表示他们之间的关系。 缓存
那么想了解 HTTP/3,QUIC 是绕不过去的,下面主要经过几个重要的特性让你们对 QUIC 有更深的理解。安全
用一张图能够形象地看出 HTTP/2 和 HTTP/3 创建链接的差异,如图2-2 和图2-3 所示。性能优化
HTTP/2 的链接须要 3 RTT,若是考虑会话复用,即把第一次握手算出来的对称密钥缓存起来,那么也须要 2 RTT,更进一步的,若是 TLS 升级到 1.3,那么 HTTP/2 链接须要 2 RTT,考虑会话复用则须要 1 RTT。有人会说 HTTP/2 不必定须要 HTTPS,握手过程还能够简化。这没毛病,HTTP/2 的标准的确不须要基于 HTTPS,但实际上全部浏览器的实现都要求 HTTP/2 必须基于 HTTPS,因此 HTTP/2 的加密链接必不可少。而 HTTP/3 首次链接只须要 1 RTT,后面的链接更是只需 0 RTT,意味着客户端发给服务端的第一个包就带有请求数据,这一点 HTTP/2 难以望其项背。那这背后是什么原理呢?结合图2-3,咱们具体看下 QUIC 的链接过程。服务器
Step1:首次链接时,客户端发送 Inchoate Client Hello 给服务端,用于请求链接;微信
Step2:服务端生成 g、p、a,根据 g、p 和 a 算出 A,而后将 g、p、A 放到 Server Config 中再发送 Rejection 消息给客户端;网络
Step3:客户端接收到 g、p、A 后,本身再生成 b,根据 g、p、b 算出 B,根据 A、p、b 算出初始密钥 K。B 和 K 算好后,客户端会用 K 加密 HTTP 数据,连同 B 一块儿发送给服务端;性能
Step4:服务端接收到 B 后,根据 a、p、B 生成与客户端一样的密钥,再用这密钥解密收到的 HTTP 数据。为了进一步的安全(前向安全性),服务端会更新本身的随机数 a 和公钥,再生成新的密钥 S,而后把公钥经过 Server Hello 发送给客户端。连同 Server Hello 消息,还有 HTTP 返回数据;
Step5:客户端收到 Server Hello 后,生成与服务端一致的新密钥 S,后面的传输都使用 S 加密。
这样,QUIC 从请求链接到正式接发 HTTP 数据一共花了 1 RTT,这 1 个 RTT 主要是为了获取 Server Config,后面的链接若是客户端缓存了 Server Config,那么就能够直接发送 HTTP 数据,实现 0 RTT 创建链接。
QUIC 实现 0 RTT 的一个技术细节是使用了 DH密钥交换算法。结合图2-4 能够更好地理解上面的过程。
TCP 链接基于四元组(源 IP、源端口、目的 IP、目的端口),切换网络时至少会有一个因素发生变化,致使链接发生变化。当链接发生变化时,若是还使用原来的 TCP 链接,则会致使链接失败,就得等原来的链接超时后从新创建链接,因此咱们有时候发现切换到一个新网络时,即便新网络情况良好,但内容仍是须要加载好久。若是实现得好,当检测到网络变化时马上创建新的 TCP 链接,即便这样,创建新的链接仍是须要几百毫秒的时间。
QUIC 的链接不受四元组的影响,当这四个元素发生变化时,原链接依然维持。那这是怎么作到的呢?道理很简单,QUIC 链接不以四元组做为标识,而是使用一个 64 位的随机数,这个随机数被称为 Connection ID,即便 IP 或者端口发生变化,只要 Connection ID 没有变化,那么链接依然能够维持。
HTTP/1.1 和 HTTP/2 都存在队头阻塞问题(Head of line blocking),那什么是队头阻塞呢?
TCP 是个面向链接的协议,即发送请求后须要收到 ACK 消息,以确认对方已接收到数据。若是每次请求都要在收到上次请求的 ACK 消息后再请求,那么效率无疑很低,如图2-5 所示。后 HTTP/1.1 提出了 Pipelining 技术,容许一个 TCP 链接同时发送多个请求,这样就大大提高了传输效率,如图2-6 所示。
HTTP/2 的多路复用解决了上述的队头阻塞问题。不像 HTTP/1.1 中只有上一个请求的全部数据包被传输完毕下一个请求的数据包才能够被传输,HTTP/2 中每一个请求都被拆分红多个 Frame 经过一条 TCP 链接同时被传输,这样即便一个请求被阻塞,也不会影响其余的请求。如图2-8 所示,不一样颜色表明不一样的请求,相同颜色的色块表明请求被切分的 Frame。
那 QUIC 是如何解决队头阻塞问题的呢?主要有两点。
拥塞控制的目的是避免过多的数据一会儿涌入网络,致使网络超出最大负荷。QUIC 的拥塞控制与 TCP 相似,并在此基础上作了改进。因此咱们先简单介绍下 TCP 的拥塞控制。
TCP 拥塞控制由 4 个核心算法组成:慢启动、拥塞避免、快速重传和快速恢复,理解了这 4 个算法,对 TCP 的拥塞控制也就有了大概了解。
QUIC 从新实现了 TCP 协议的 Cubic 算法进行拥塞控制,并在此基础上作了很多改进。下面介绍一些 QUIC 改进的拥塞控制的特性。
TCP 中若是要修改拥塞控制策略,须要在系统层面进行操做。QUIC 修改拥塞控制策略只须要在应用层操做,而且 QUIC 会根据不一样的网络环境、用户来动态选择拥塞控制算法。
TCP 为了保证可靠性,使用 Sequence Number 和 ACK 来确认消息是否有序到达,但这样的设计存在缺陷。
超时发生后客户端发起重传,后来接收到了 ACK 确认消息,但由于原始请求和重传请求接收到的 ACK 消息同样,因此客户端就郁闷了,不知道这个 ACK 对应的是原始请求仍是重传请求。若是客户端认为是原始请求的 ACK,但其实是图2-11 的情形,则计算的采样 RTT 偏大;若是客户端认为是重传请求的 ACK,但其实是图2-12 的情形,又会致使采样 RTT 偏小。图中有几个术语,RTO 是指超时重传时间(Retransmission TimeOut),跟咱们熟悉的 RTT(Round Trip Time,往返时间)很长得很像。采样 RTT 会影响 RTO 计算,超时时间的准确把握很重要,长了短了都不合适。
TCP 计算 RTT 时没有考虑接收方接收到数据到发送确认消息之间的延迟,如图2-15 所示,这段延迟即 ACK Delay。QUIC 考虑了这段延迟,使得 RTT 的计算更加准确。
通常来讲,接收方收到发送方的消息后都应该发送一个 ACK 回复,表示收到了数据。但每收到一个数据就返回一个 ACK 回复太麻烦,因此通常不会当即回复,而是接收到多个数据后再回复,TCP SACK 最多提供 3 个 ACK block。但有些场景下,好比下载,只须要服务器返回数据就好,但按照 TCP 的设计,每收到 3 个数据包就要“礼貌性”地返回一个 ACK。而 QUIC 最多能够捎带 256 个 ACK block。在丢包率比较严重的网络下,更多的 ACK block 能够减小重传量,提高网络效率。
TCP 会对每一个 TCP 链接进行流量控制,流量控制的意思是让发送方不要发送太快,要让接收方来得及接收,否则会致使数据溢出而丢失,TCP 的流量控制主要经过滑动窗口来实现的。能够看出,拥塞控制主要是控制发送方的发送策略,但没有考虑到接收方的接收能力,流量控制是对这部分能力的补齐。
QUIC 只须要创建一条链接,在这条链接上同时传输多条 Stream,比如有一条道路,两头分别有一个仓库,道路中有不少车辆运送物资。QUIC 的流量控制有两个级别:链接级别(Connection Level)和 Stream 级别(Stream Level),比如既要控制这条路的总流量,不要一会儿不少车辆涌进来,货物来不及处理,也不能一个车辆一会儿运送不少货物,这样货物也来不及处理。
那 QUIC 是怎么实现流量控制的呢?咱们先看单条 Stream 的流量控制。Stream 还没传输数据时,接收窗口(flow control receive window)就是最大接收窗口(flow control receive window),随着接收方接收到数据后,接收窗口不断缩小。在接收到的数据中,有的数据已被处理,而有的数据还没来得及被处理。如图2-16 所示,蓝色块表示已处理数据,黄色块表示未处理数据,这部分数据的到来,使得 Stream 的接收窗口缩小。
随着数据不断被处理,接收方就有能力处理更多数据。当知足 (flow control receive offset - consumed bytes) < (max receive window / 2) 时,接收方会发送 WINDOW_UPDATE frame 告诉发送方你能够再多发送些数据过来。这时 flow control receive offset 就会偏移,接收窗口增大,发送方能够发送更多数据到接收方。
接收窗口(flow control receive window) = 最大接收窗口(max receive window) - 已接收数据(highest received byte offset)
,而对 Connection 来讲:
接收窗口 = Stream1接收窗口 + Stream2接收窗口 + ... + StreamN接收窗口
。
由于不确认服务器是否支持 QUIC,因此须要经历协商升级过程才能决定可以使用 QUIC。
首次请求时,客户端会使用 HTTP/1.1 或者 HTTP/2,若是服务器支持 QUIC,则在响应的数据中返回 alt-svc 头部,主要包含如下信息:
确认服务器支持 QUIC 以后,客户端向服务端同时发起 QUIC 链接和 TCP 链接,比较两个链接的速度,而后选择较快的协议,这个过程叫“竞速”,通常都是 QUIC 获胜。
目前 Google、Gmail、QQ 会员等业务已经陆续使用 QUIC。本文主要侧重介绍 QUIC 自己,也限于笔者这方面实践经验有限,QUIC 应用部分再也不详述,你们能够找相关实践文章,好比这篇《让互联网更快的协议,QUIC在腾讯的实践及性能优化》。
QUIC 丢掉了 TCP、TLS 的包袱,基于 UDP,并对 TCP、TLS、HTTP/2 的经验加以借鉴、改进,实现了一个安全高效可靠的 HTTP 通讯协议。凭借着零 RTT 创建链接、平滑的链接迁移、基本消除了队头阻塞、改进的拥塞控制和流量控制等优秀的特性,QUIC 在绝大多数场景下得到了比 HTTP/2 更好的效果,HTTP/3 将来可期。