最近在组会上讲了关于HTTP3的相关知识,搜集了很多网上的文章,现在整理出来分享给大家,一起保持技术嗅觉
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协议的升级完全不依赖于底层操作系统,只需终端和服务器升级到指定版本即可。
由此带来的好处和能解决的问题如下:
- 更快的协议更新。应用程序层面就能实现不同的拥塞控制算法,不需要操作系统,不需要内核支持。这是一个飞跃,因为传统的 TCP 拥塞控制,必须要端到端的网络协议栈支持,才能实现控制效果。而内核和操作系统的部署成本非常高,升级周期很长,这在产品快速迭代,网络爆炸式增长的今天,显然有点满足不了需求。比如 TCP Fast Open,虽然在2013 年就被提出了,但是 Windows 很多系统版本依然不支持它。
- 针对不同网络的用户提供不同的拥塞控制。单个应用程序的不同连接能支持配置不同的拥塞控制。就算是一台服务器,接入的用户网络环境也千差万别,结合大数据及人工智能处理,我们能为各个用户提供不同的但又更加精准更加有效的拥塞控制。
- 在线热更新。应用程序不需要停机和升级就能实现拥塞控制的变更,我们在服务端只需要修改一下配置,reload 一下,完全不需要停止服务就能实现拥塞控制的切换。
QUIC的整体架构
QUIC基于UDP协议实现了类似TCP+TLS+HTTP2的功能组合,可以把QUIC和现有的协议理解成以下结构:
对比HTTP2的优势
- 降低建立连接的延迟,最好情况下是0 RTT,即不需要建立连接
- 连接迁移(无需考虑用户网络环境的切换)
- 无队头阻塞的多路复用
- 错误自动纠正
- 改进的拥塞控制
下面我们一条条来说:
0 RTT建立连接
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 最基本的传输单元是 Packet,整个加密和认证过程都是基于 Packet 的,不会跨越多个 Packet。这样就能避免 TLS 协议存在的队头阻塞。
- Stream 之间相互独立,比如 Stream2 丢了一个 Pakcet,不会影响 Stream3 和 Stream4。
这里要提及一下,QUIC也是有可能存在队头阻塞的,若QUIC 使用 Hpack 压缩算法,由于算法的限制,丢失一个头部数据时,可能遇到队头阻塞。
总体来说,QUIC 在传输大量数据时,比如视频,受到队头阻塞的影响很小。
QUIC 所带来的挑战
99%+以上的手机移动终端、电脑终端,都使用私有IP,都需要NAT设备来完成私有IP与全球IP的转换。这意味着NAT设备通常会记忆用户的通信状态,一旦用户完成了通信,NAT设备会释放这些记忆。
对于基于TCP的HTTP、HTTPS传输,NAT设备可以根据TCP报文头的SYN / FIN状态位,知道通信什么时候开始,什么时候结束,对应记忆的开始、记忆的结束。
但是基于UDP传输的HTTP/3,NAT设备收到流量会知道连接什么时候开始,但是却无法知道流量什么时候结束。
- NAT设备的记忆如果短于用户会话时间,则用户会话会中断。
- NAT设备的记忆如果大大长于用户会话时间,则意味着NAT设备的端口资源会白白被占用!
最有效的解决方案,是让QUIC周期性地发送Keepalive消息,刷新NAT设备的记忆,避免NAT设备释放自己的记忆。
我的感想
- 未来网络协议的发展已经被几家大公司主导的了,广大开发者需要耐心等待协议的升级。
- 网络协议的更新应当是能够被快速更新的,这样才能适应这个快速变化的时代。
- 协议的更新是有迹可循的,善用前人留下的财富才是王道。TCP所遗留下来的东西没有被抛弃,只是实现从传输层移动到了应用层。
限于篇幅,本文不再详细介绍QUIC的细节,有兴趣的可以参考下面的引用资料。
参考资料:
- https://www.nanog.org/sites/default/files//meetings/NANOG64/1051/20150603_Rogan_Quic_Next_Generation_v1.pdf
- https://docs.google.com/document/d/1gY9-YNDNAB1eip-RTPbqphgySwSNSDHLq9D5Bty4FSU/edit
- https://genuifx.github.io/2018/11/27/keynote-for-http3-quic/
- https://zhuanlan.zhihu.com/p/27938635
- https://www.zhihu.com/question/302412059/answer/533223530
- https://zhuanlan.zhihu.com/p/32553477