首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >QUIC:下一代通信协议

QUIC:下一代通信协议

作者头像
QQ音乐前端团队
发布2021-11-22 10:40:13
8140
发布2021-11-22 10:40:13
举报

一. 前言

自 2015 年以来,QUIC 协议开始在 IETF 进行标准化并被国内外各大厂商相继落地。
鉴于 QUIC 具备“0RTT 建连”、“支持连接迁移”等诸多优势,即将成为下一代互联网协议。

阅读完本文你将了解和学习到:

  1. HTTP协议发展史
  2. HTTP各版本存在的问题,以及各版本解决了哪些问题
  3. QUIC协议特性
  4. 再也不怕面试官问HTTP相关的问题了!

行文思路: 从历史使用最广泛的HTTP1.1开始,介绍各版本存在的问题,以及新版本如何解决旧版本存在的问题

二. HTTP协议发展史

  1. HTTP 0.9(1991年)只支持get方法不支持请求头
  2. HTTP 1.0(1996年)基本成型,支持请求头、富文本、状态码、缓存、连接无法复用
  3. HTTP 1.1(1999年)支持连接复用、分块发送、断点续传
  4. HTTP 2.0(2015年)二进制分帧传输、多路复用、头部压缩、服务器推送等
  5. HTTP 3.0(2018年)QUIC 于2013年实现;2018年10月,IETF的HTTP工作组和QUIC工作组共同决定将QUIC上的HTTP映射称为 "HTTP/3[1]",以提前使其成为全球标准

HTTP1.1存在的问题

1. 单向请求

只能单向请求,必须由客户端先发起请求,服务器才能发送数据给客户端,不能主动推送数据给客户端。

2. 协议开销大

header里携带的内容过大,且不能压缩,增加了传输的成本。 举个栗子:客户端每次发起请求,都要在请求头里带上 Cache-Control: no-cache。每次请求都重复带上这些字段,其实是带宽浪费的

3. 队头阻塞

下个请求必须在前一个请求返回后才能发出,导致带宽无法被充分利用,后续请求被阻塞(HTTP 1.1 尝试使用流水线(Pipelining)技术,但先天 FIFO(先进先出)机制导致当前请求的执行依赖于上一个请求执行的完成,容易引起队头阻塞,并没有从根本上解决问题) 举个栗子:请求 A 和请求 B。A 先被发起,此时 server 端接收到了 A 请求,正在处理。同时 B 请求也发过来了。但是 A 请求还没被返回,此时 B 请求只能等待。

基于HTTP1.1存在的这些问题,HTTP2在2015年被提出。HTTP2解决了HTTP1.1的哪些问题?又有哪些新的特性?

HTTP2特性以及存在的问题

HTTP2特性
1. 二进制分帧

在 HTTP 2.0 中,它把数据报的两大部分分成了 header frame 和 data frame。也就是头部帧和数据体帧。帧的传输最终在流中进行,流中的帧,头部(header)帧 和 data 帧可以分为多个片段帧,例如data帧即是可以 data = data_1 + data_2 + ... + data_n

举个栗子:请求a.js和b.css,a.js对应的stream的id为1,b.css对应的stream的id为2,a.js的head帧为head1,数据帧为data1,b.js的head帧为head2,数据帧为data2。浏览器可以将head1、data1、head2、data2同时放入TCP信道进行报文传输,在TCP层,可能会进一步对这些数据进行拆分,拆成不同报文序号进行传输,但是可以无需关注这层是如何拆分、组装的。因为可以在HTTP2.0的二进制帧层进行有序处理,将接收到的stream的id为1的放一起处理,接收到的stream的id为2的放一起处理。

2. 多路复用

HTTP 2.0 的多路复用其实是 HTTP 1.1 中长链接的升级版本,在 HTTP 1.1 中,一次链接成功后,只要该链接还没断开,那么 client 端可以在这么一个链接中有序地发起多个请求,并以此获得每个请求对应的响应数据。它的缺点是,一次请求与响应的交互必须要等待前面的请求交互完成,否则后面的只能等待,这个就是HTTP层面的头阻塞。在 HTTP 2.0 中,一次链接成功后,只要链接还没断开,那么 client 端就可以在一个链接中并发地发起多个请求,每个请求及该请求的响应不需要等待其他的请求,某个请求任务耗时严重,不会影响到其它连接的正常执行

3. 流优先级

WEB应用的资源有重要性的区别,优先加载重要资源,可以尽快渲染页面,提升用户体验。HTTP2中,所有资源通过一个连接传输,为了避免队头阻塞,这时候资源传输的顺序就更重要了。

举个栗子:如果一个网页上有一堆图片,还有一个外部样式表。如果浏览器首先下载了所有图片并且最后加载了样式表,在所有内容都加载完毕前,页面将完全是空白页,这谁受得了啊?

4. Header压缩

减少请求中的冗余数据,降低开销,使用的压缩算法为HPACK,这种算法通过服务端和客户端个字维护索引表来实现。

举个例子:客户端和服务器通信,类似于 A 和 B 讲话,A 每天中午都要和 B 说:“走去万达广场吃饭了”,每天都这么说就很累,A 不想每次都说这么多字,就和 B 约定好,以后 A 说 “吃”,就代表 “走去万达广场吃饭了”,从那以后每天中午吃饭 A 就只用跟 B 说一个“吃”字,B 就知道 A 叫他去万达吃饭了。

5. 服务端主动推送

提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间

举个栗子:当客户端请求一个HTML文件,服务器返回这个HTML之前,其实是可以解析出这个HTML引用了哪些JS文件和CSS文件的,那服务器就可以主动推送这些静态资源文件给客户端,而不用等客户端收到HTML之后,解析HTML引用的静态资源,再请求后端,这样就节省了一些时间。

HTTP2存在的问题
1. 建立连接耗时长

建连耗时长,主要指的是TCP的三次握手,还有TLS建连耗时长,这里简单了解就行。在下文QUIC 0RTT 建连的部分会把HTTP2和QUIC进行对比,并深入讲解

2. 队头阻塞

实际上多路复用只是解决了HTTP层面的队头堵塞,TCP层面的队头堵塞依然存在,在下文QUIC解决队头阻塞的部分会把HTTP2和QUIC进行对比,并深入讲解

基于HTTP2存在的这些问题,google另辟蹊径,设计了QUIC协议,并在2018年被正式提议为HTTP3

三. QUIC协议

什么是QUIC?

QUIC(Quick UDP Internet Connection)是谷歌推出的一套基于UDP的传输协议,它实现了TCP + HTTPS + HTTP/2的功能,目的是保证可靠性的同时降低网络延迟。 从上图可以看出来,QUIC运行在不可靠的 UDP 协议之上。但是,这并不意味着 QUIC 本身也是不可靠的!在某种程度上,QUIC 应该被看作是一个 TCP 2.0。它包括 TCP 的所有特性(可靠性、拥塞控制、流量控制、排序等)的最佳版本,以及更多其他特性。QUIC还完全集成了TLS,不允许未加密的连接。

QUIC协议特性

1. 基于UDP

HTTP2.0及之前的版本,传输层都是使用TCP的,而也正是因为使用了TCP,建立连接时就必须进行TCP的三次握手,导致建连耗时过长。 QUIC是基于UDP的,而UDP本身的特性就是无链接,这样就节省了建连时间

2. 低连接延时
HTTP2建连耗时高的问题

image.png

由上图可以看出,HTTP2在首次建立连接时,需要3次RTT时间,而在非首次建连(已经交换过TLS密钥),并且使用最快的TLS1.3也至少需要1次RTT时间

举个栗子:一次简单的浏览器访问,在地址栏中输入某个网址按下回车,实际会产生以下动作:

  1. DNS递归查询www.abc.com,获取地址解析的对应IP;
  2. TCP握手,我们熟悉的TCP三次握手需要1个RTT(也可以算作1.5,因为是1个半往返延时);
  3. TLS握手,以目前应用最广泛的TLS 1.2而言,需要2个RTT。对于非首次建连,可以选择启用会话重用,则可缩小握手时间到1个RTT。由于本文核心内容是HTTP,不会详细讲解TLS建连的过程,不懂的自行google即可
  4. HTTP业务数据交互,假设abc.com的数据在一次交互就能取回来。那么业务数据的交互需要1个RTT;经过上面的过程分析可知,要完成一次简短的HTTPS业务数据交互,需要经历:新连接 3RTT + DNS;会话重用 1RTT + DNS。

所以,对于数据量小的请求而言,单一次的请求握手就占用了大量的时间,对于用户体验的影响非常大。同时,在用户网络不佳的情况下,RTT延时会变得较高,极其影响用户体验。

QUIC的0-RTT建立连接

image.png

QUIC基于UDP,其实本身是不需要建立连接的,建连主要是为了交换TLS密钥。这里建连主要分两个场景:

  1. 首次建立连接(1RTT),需要交换加密密钥,再发送业务数据
  2. 非首次建立链接(0RTT),已经交换过加密密钥,直接发送业务数据 而非首次实现1RTT建连的核心是使用了Diffie-Hellman算法进行密钥交换,DH算法是一个密钥协商算法,双方最终协商出一个共同的密钥,而这个密钥不会通过网络传输。 如下图栗子:

Alice和Bob要交换数据,Alice首先选择一个素数p,底数g,随机数小a,然后计算A=g^a mod p,生成大A,然后Alice把底数g,素数p,和计算出来的大A发送给Bob;Bob收到后,也选择一个随机数b,然后计算B=g^b mod p,计算出大B,乙再同时计算K=A^b mod p, Bob把计算出来的大B发给Alice,Alice通过这个公式,就能计算出K,计算结果与Bob算出的结果一样

我们把小a看成Alice的私钥,大A看成Alice的公钥,小b看成Bob的私钥,大B看成Bob的公钥,DH算法的本质就是双方各自生成自己的私钥和公钥,私钥仅对自己可见,然后交换公钥,并根据自己的私钥和对方的公钥,生成最终的密钥secretKey,DH算法通过数学定律保证了双方各自计算出的secretKey是相同的。

可以发现最终的密钥K并没有在网络中传输过,但是双方仅通过交换公钥,就可以计算协商出加密数据用的K

3. 连接迁移
HTTP2基于四元组标识连接

当四元组中的任何一个元素变化,都会导致连接断开,需要重新建立连接 举个例子:当用户从 WIFI 切换到 4G 场景,基于 TCP 的 HTTP 协议无法保持连接的存活,因为四元组里的元素变化了

QUIC基于ConnectionID标识连接

那 QUIC 是如何做到连接迁移呢?很简单,QUIC是基于UDP协议的,任何一条 QUIC 连接不再以 IP 及端口四元组标识,而是以一个 64 位的随机数作为 ID 来标识,这样就算 IP 或者端口发生变化时,只要 ID 不变,这条连接依然维持着,上层业务逻辑感知不到变化,不会中断,也就不需要重连。由于这个 ID 是客户端随机产生的,并且长度有 64 位,所以冲突概率非常低。

4. 可自定义的拥塞控制

传输层协议如 TCP 和 QUIC 包括一种称为拥塞控制(Congestion Control)的机制。比如:慢启动,拥塞避免,快速重传,快速恢复。 拥塞控制器的主要工作是确保网络不会同时被过多的数据过载。如果没有缓冲区的话,数据包就会溢出, 所以,它通常只发送一点数据(通常是 14KB),看看是否能通过。如果数据到达,接收方将确认发送回发送方。只要所有发送的数据都得到确认,发送方就在每次 RTT 时将其发送速率加倍,直到观察到丢包事件(这意味着网络过载(1 位),它需要后退(1 位))。这就是 TCP 连接如何“探测”其可用带宽

HTTP2.0 在TCP底层固化使用了Cubic拥塞控制算法,无法改变,灵活性差

QUIC 的传输控制不再依赖内核的拥塞控制算法,而是实现在应用层上,这意味着我们根据不同的业务场景,实现和配置不同的拥塞控制算法以及参数。GOOGLE 提出的 BBR 拥塞控制算法与 CUBIC 是思路完全不一样的算法,在弱网和一定丢包场景,BBR 比 CUBIC 更不敏感,性能也更好。在 QUIC 下我们可以根据业务随意指定拥塞控制算法和参数,甚至同一个业务的不同连接也可以使用不同的拥塞控制算法。

5. 无队头阻塞
HTTP2存在队头阻塞问题

如上图的例子,HTTP2 在一个 TCP 连接上同时发送 4 个 Stream。其中 Stream1 已经正确到达,并被应用层读取。但是 Stream2 的第三个 tcp segment 丢失了,TCP 为了保证数据的可靠性,需要发送端重传第 3 个 segment 才能通知应用层读取接下去的数据,虽然这个时候 Stream3 和 Stream4 的全部数据已经到达了接收端,但都被阻塞住了,这就是TCP层面的队头阻塞问题

请深刻理解这句话:虽然我们和浏览器都知道我们正在获取 JavaScript 和 CSS 文件,但 HTTP/2 不需要知道这一点。它只知道它在使用来自不同资源流 id (stream id)的块。然而,TCP 甚至不知道它在传输 HTTP!

怎么解决TCP队头阻塞的问题?其实解决方案很简单:我们“只是”需要让传输层知道不同的、独立的流。这样,如果一个流的数据丢失,传输层本身就知道它不需要阻塞其他流。尽管这个解决方案概念简单,但在现实中却很难实现。由于各种原因,改变 TCP 本身使其具有流意识(stream-aware)已经非常困难了

QUIC解决了HTTP2的队头阻塞问题

QUIC 受到 HTTP2 帧方式(framing-approach)的启发,还添加了自己的帧(frames)。流id(stream id)以前在 HTTP2 的数据帧(DATA frame)中,现在被下移到传输层的 QUIC 流帧(STREAM frame)中,同时QUIC 使用的Packet Number 单调递增的设计,可以让数据包不再像TCP 那样必须有序确认,QUIC 支持乱序确认,当数据包Packet N 丢失后,只要有新的已接收数据包确认,当前窗口就会继续向右滑动。待发送端获知数据包Packet N 丢失后,会将需要重传的数据包放到待发送队列,重新编号比如数据包Packet N+M 后重新发送给接收端,对重传数据包的处理跟发送新的数据包类似,这样就不会因为丢包重传将当前窗口阻塞在原地,从而解决了队头阻塞问题。那么,既然重传数据包的Packet N+M 与丢失数据包的Packet N 编号并不一致,我们怎么确定这两个数据包的内容一样呢?QUIC使用Stream ID 来标识当前数据流属于哪个资源请求,这同时也是数据包多路复用传输到接收端后能正常组装的依据。重传的数据包Packet N+M 和丢失的数据包Packet N 单靠Stream ID 的比对一致仍然不能判断两个数据包内容一致,还需要再新增一个字段Stream Offset,标识当前数据包在当前Stream ID 中的字节偏移量。有了Stream Offset 字段信息,属于同一个Stream ID 的数据包也可以乱序传输了(HTTP/2 中仅靠Stream ID 标识,要求同属于一个Stream ID 的数据帧必须有序传输),通过两个数据包的Stream ID 与 Stream Offset 都一致,就说明这两个数据包的内容一致。

总结

现今网络带宽已经大幅提升的情况下,传输的数据大小已经不是主要的性能瓶颈,反而网络延时变得更为重要。基于这个背景,QUIC 不再使用TCP作为传输层协议,而是另辟蹊径采用了UDP,适应了时代网络状况的变化。在某种程度上,QUIC 应该被看作是一个 TCP 2.0。它包括 TCP 的所有特性(可靠性、拥塞控制、流量控制、排序等)的最佳版本,以及更多其他特性。QUIC还完全集成了TLS,不允许未加密的连接。同时拥有 0 RTT 建立连接、平滑的连接迁移、基本消除了队头阻塞、改进的拥塞控制和流量控制等特性,我相信QUIC前途无量。

参考文章

  1. HTTP/3 From A To Z: Core Concepts
  2. QUIC官网
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 QQ音乐前端团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 前言
  • 二. HTTP协议发展史
    • HTTP1.1存在的问题
      • 1. 单向请求
      • 2. 协议开销大
      • 3. 队头阻塞
    • HTTP2特性以及存在的问题
      • HTTP2特性
      • HTTP2存在的问题
  • 三. QUIC协议
    • 什么是QUIC?
      • QUIC协议特性
        • 1. 基于UDP
        • 2. 低连接延时
        • 3. 连接迁移
        • 4. 可自定义的拥塞控制
        • 5. 无队头阻塞
      • 总结
        • 参考文章
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档