最近在组会上讲了关于HTTP3的相关知识,搜集了很多网上的文章,现在整理出来分享给大家,一起保持技术嗅觉
在HTTP协议的发展历程上,Google毫无疑问稳坐第一把手,在坐拥Google搜索、Gmail、Youtube等一众全球流量最大的几个网站的过程中,Google不断推陈出新,以一己之力推动着HTTP的发展,从HTTP-over-SPDY被吸纳为HTTP/2,到如今HTTP-over-QUIC被吸纳为HTTP/3,Google可以说是独领风骚。
QUIC (Quick UDP Internet Connections) 是由google开发的新一代网络传输协议,初衷是利用工程师几十年的经验来改进网络传输延迟。(以下的QUIC均指代HTTP3)
在HTTP2已经如此强大的背景下,为什么还会有HTTP3的出现呢?首先我们要知道的是,TCP/IP出现是在上个世纪70年代,那个时候用户的网络情况单一(有线),网络丢包时有发生,协议的更新非常缓慢,但经过了40多年的迅猛发展,现在网络的情况已经比当时好了太多,且用户的网络环境变得更加多样(4G、wifi、有线)且时常会有切换网络的情况发生,这种情况下想要去更新协议就非常困难,因为TCP这类网络协议栈的实现本来就依赖系统内核更新,而不管是终端设备,中间设备的系统更新都极其缓慢,一个更新可能需要5-15年的时间去普及。
举个例子,浏览器的兼容性问题和速度花费了7年时间,才把IE 6、7这类需要各种奇技淫巧去hack的浏览器给淘汰掉。
于是QUIC的设计师们决定在传输层抛弃TCP,拥抱UDP协议。UDP不需要握手建立连接,至于什么防止IP攻击,怎么保证数据一致性,这些都是UDP之上的工作,把很多工作从传输层移动到了应用层,如此一来,以后QUIC协议的升级完全不依赖于底层操作系统,只需终端和服务器升级到指定版本即可。
由此带来的好处和能解决的问题如下:
QUIC基于UDP协议实现了类似TCP+TLS+HTTP2的功能组合,可以把QUIC和现有的协议理解成以下结构:
下面我们一条条来说:
RTT(round-trip time)顾名思义,就是服务器和终端一次交互需要的时间。
传统的TCP协议,我们需要进行3次握手,也就是1.5 RTT,才开始传输数据。开启HTTP/2时,我们通常要建立连接,还要确定好加密版本,加密密钥等信息,TCP+TLS需要3 RTT,其中TCP耗费了1.5 RTT(三次握手),TLS耗费了1.5 RTT。
0 RTT 的效果是因为QUIC的客户端会缓存服务器端发的令牌和证书,当有数据需要再次发送的时候,客户端可以直接使用旧的令牌和证书,这样子就实现了 0 RTT 了。对于没有缓存的情况,服务器端会直接拒绝请求,并且返回新生产的令牌和证书。 所以当令牌失效或者没有缓存的情况下,QUIC还是需要一次握手才能开始传输数据。
所以这么做的话,那么防范重放攻击(replay attack)就要从应用层着手了。
一条 TCP 连接是由四元组标识的(源 IP,源端口,目的 IP,目的端口)。连接迁移指的就是当其中任何一个元素发生变化时,这条连接依然维持着,能够保持业务逻辑不中断。这里面主要关注的是客户端的变化,因为客户端不可控并且网络环境经常发生变化,而服务端的 IP 和端口一般都是固定的。
比如大家使用手机在 WIFI 和 4G 移动网络切换时,客户端的 IP 肯定会发生变化,需要重新建立和服务端的 TCP 连接。
那 QUIC 是如何做到连接迁移呢?很简单,任何一条 QUIC 连接不再以 IP 及端口四元组标识,而是以一个 64 位的随机数作为 ID 来标识,这样就算 IP 或者端口发生变化时,只要 ID 不变,这条连接依然维持着,上层业务逻辑感知不到变化,不会中断,也就不需要重连,且这个 ID 是客户端随机产生的,并且长度有 64 位,所以冲突概率非常低。
多路复用是 HTTP2 最强大的特性,但QUIC 的多路复用相比 HTTP2 有一个很大的优势。
QUIC 一个连接上的多个 stream(HTTP 请求) 之间没有依赖。这样假如 stream2 丢了一个 udp packet,也只会影响 stream2 的处理。不会影响 stream2 之前及之后的 stream 的处理。
这也就在很大程度上缓解甚至消除了队头阻塞的影响。
如上图所示,HTTP2 在一个 TCP 连接上同时发送 4 个 Stream。其中 Stream1 已经正确到达,并被应用层读取。但是 Stream2 的第三个 tcp segment 丢失了,TCP 为了保证数据的可靠性,需要发送端重传第 3 个 segment 才能通知应用层读取接下去的数据,虽然这个时候 Stream3 和 Stream4 的全部数据已经到达了接收端,但都被阻塞住了。
不仅如此,由于 HTTP2 强制使用 TLS,还存在一个 TLS 协议层面的队头阻塞。
QUIC 的多路复用为什么能避免上述问题呢?
这里要提及一下,QUIC也是有可能存在队头阻塞的,若QUIC 使用 Hpack 压缩算法,由于算法的限制,丢失一个头部数据时,可能遇到队头阻塞。
总体来说,QUIC 在传输大量数据时,比如视频,受到队头阻塞的影响很小。
99%+以上的手机移动终端、电脑终端,都使用私有IP,都需要NAT设备来完成私有IP与全球IP的转换。这意味着NAT设备通常会记忆用户的通信状态,一旦用户完成了通信,NAT设备会释放这些记忆。
对于基于TCP的HTTP、HTTPS传输,NAT设备可以根据TCP报文头的SYN / FIN状态位,知道通信什么时候开始,什么时候结束,对应记忆的开始、记忆的结束。
但是基于UDP传输的HTTP/3,NAT设备收到流量会知道连接什么时候开始,但是却无法知道流量什么时候结束。
最有效的解决方案,是让QUIC周期性地发送Keepalive消息,刷新NAT设备的记忆,避免NAT设备释放自己的记忆。
限于篇幅,本文不再详细介绍QUIC的细节,有兴趣的可以参考下面的引用资料。