HTTP

超文本传输协议(HyperText Transfer Protocol) ,伴随着计算机网络和浏览器的诞生,HTTP1.0也随之而来,处于计算机网络中的应用层,HTTP是建立在TCP协议之上,所以HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性,例如tcp建立连接的3次握手和断开连接的4次挥手以及每次建立连接带来的RTT延迟时间。

早在HTTP建立之初,主要就是为了将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器。也是说对于前端来说,我们所写的HTML页面将要放在我们的web服务器上,用户端通过浏览器访问url地址来获取网页的显示内容,但是到了WEB2.0以来,我们的页面变得复杂,不仅仅单纯的是一些简单的文字和图片,同时我们的HTML页面有了CSS,Javascript,来丰富我们的页面展示,当ajax的出现,我们又多了一种向服务器端获取数据的方法,这些其实都是基于HTTP协议的。 同样到了移动互联网时代,我们页面可以跑在手机端浏览器里面,但是和PC相比,手机端的网络情况更加复杂,这使得我们开始了不得不对HTTP进行深入理解并不断优化过程中。

2022_03_20_zuTARk

影响一个HTTP网络请求的因素主要有两个:带宽和延迟。

  • 带宽:如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,但是现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。
  • 延迟

1.浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制,后续请求就会被阻塞。

2.DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。

3.建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动open in new window。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。

HTTP1.x

HTTP1.0

在http1.0时代,每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接。TCP连接的新建成本很高,因为需要客户端和服务器三次握手,并且开始时发送速率较慢(TCP的拥塞控制开始时会启动慢启动算法)。

在数据传输的开始只能发送少量包,并随着网络状态良好(无拥塞)指数增长。但遇到拥塞又要重新从1个包开始进行传输。

以下图为例,慢启动时第一次数据传输只能传输一组数据,得到确认后传输2组,每次翻倍,直到达到阈值16时开始启用拥塞避免算法,既每次得到确认后数据包只增加一个。当发生网络拥塞后,阈值减半重新开始慢启动算法。

2022_03_09_IKvpuw

因此为避免tcp连接的三次握手耗时及慢启动引起的发送速度慢的情况,应尽量减少tcp连接的次数。

而HTTP1.0每个数据请求都需要重新建立连接的特点使得HTTP 1.0版本的性能比较差。随着网页加载的外部资源越来越多,这个问题就愈发突出了。 为了解决这个问题,有些浏览器在请求时,用了一个非标准的Connection字段。Kepp-alive 一个可以复用的TCP连接就建立了,直到客户端或服务器主动关闭连接。但是,这不是标准字段,不同实现的行为可能不一致,因此不是根本的解决办法。

HTTP1.1

http1.1(以下简称h1.1) 版的最大变化,就是引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive

客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。不过,规范的做法是,客户端在最后一个请求时,发送Connection: close,明确要求服务器关闭TCP连接。 目前,对于同一个域名,大多数浏览器允许同时建立6个持久连接。相比与http1.0,http1.1的页面性能有了巨大提升,因为省去了很多tcp的握手挥手时间。

下图第一种是tcp建立后只能发一个请求的http1.0的通信状态,而拥有了持久连接的h1.1则避免了tcp握手及慢启动带来的漫长时延。

2022_03_09_X8VTnb

从图中可以看到相比http1.0,http1.1的性能有所提升。

然而虽然1.1版允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为**"队头堵塞"(Head-of-line blocking)**。

为了避免这个问题,只有三种方法:

  • 减少请求数;

  • 同时多开持久连接;这导致了很多的网页优化技巧,比如合并脚本和样式表、将图片嵌入CSS代码、域名分片(domain sharding)等等。如果HTTP协议能继续优化,这些额外的工作是可以避免的。

  • 开启管道链接(pipelining),不过pipelining并不是救世主,它也存在不少缺陷:

    • pipelining只能适用于http1.1,一般来说,支持http1.1的server都要求支持pipelining。
    • 只有幂等的请求(GET,HEAD)能使用pipelining,非幂等请求比如POST不能使用,因为请求之间可能会存在先后依赖关系。
    • 队头堵塞(Head-of-line blocking)并没有完全得到解决,server的response还是要求依次返回,遵循FIFO(first in first out)原则。也就是说如果请求1的response没有回来,2,3,4,5的response也不会被送回来。
    • 绝大部分的http代理服务器不支持pipelining。 和不支持pipelining的老服务器协商有问题。 可能会导致新的队首阻塞问题。

    鉴于以上种种原因,pipelining的支持度并不友好。

    2022_03_09_5awrSH

HTTPS

HTTP1.x在传输数据时,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份,这在一定程度上无法保证数据的安全性。

为了解决以上问题,网景在1994年创建了HTTPS,并应用在网景导航者浏览器中。 最初,HTTPS是与SSL一起使用的;在SSL(Secure Sockets Layer)逐渐演变到TLS(Transport Layer Security)时(其实两个是一个东西,只是名字不同而已)。

HTTPS全称Hypertext Transfer Protocol Secure,是在HTTP协议中添加TLS/SSL扩展,支持加密数据传输。确保数据在传输过程中不被读取(加密算法)、篡改(数字签名)、调包(数字证书)。

HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。

加密算法

Https采用混合加密机制,如果密钥能够保证安全交换,那么全程有可能仅使用对称密钥加密来进行通信,如果不能保证密钥安全交换,可在密钥交换环节使用非对称加密方式,之后使用对称加密方式。 这样做的目的是因为对称密钥加密相较非对称秘钥加密处理速度更快。

1.对称加密

对称加密算法的特点是算法公开、计算量小、加密速度快、加密效率高。对称加密有很多种算法,由于它效率很高,所以被广泛使用在很多加密协议的核心当中。不足之处是,交易双方都使用同样钥匙,安全性得不到保证。常见的对称加密有 DES、AES 等。

2.非对称加密

非对称加密使用一对“私钥-公钥”,用私钥加密的内容只有对应公钥才能解开,反之亦然。非对称加密有以下特性:

  • 对于一个公钥,有且只有一个对应的私钥。
  • 公钥是公开的,并且不能通过公钥反推出私钥。
  • 通过私钥加密的密文只能通过公钥能解密,通过公钥加密的密文也只能通过私钥能解密。

非对称加密不需要共享同一份秘钥,安全性要比对称加密高,但由于算法强度比对称加密复杂,加解密的速度比对称加解密的速度要慢。常见的非对称加密有 RSA、ESA、ECC 等。

数字签名

数字签名就是用摘要算法提取出源文件的摘要并用私钥进行加密后的内容。摘要一致则说明文件没有被篡改过,即使摘要被解密也无法还原数据内容。 摘要算法有以下特性:

  • 只要源文本不同,计算得到的结果,必然不同(或者说机会很少)。
  • 无法从结果反推出源数据。

一般使用摘要算法来校验原始内容是否被篡改。常见的摘要算法有 MD5、SHA 等。

数字证书

数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件,用于认证密匙的有效性。一般会包含公钥、公钥拥有者名称、CA 的数字签名、有效期、授权中心名称、证书序列号等信息。

数字证书如何确保列出的用户就是公钥的拥有者呢?关键点是 CA 的数字签名,CA会用自己的私钥将证书内容的摘要进行加密。因为 CA 的公钥是公开的,任何人都可以用公钥解密出 CA 的数字签名的摘要,再用同样的摘要算法提取出证书的摘要和解密 CA 数字签名后的摘要比对,一致则说明这个证书没有被篡改过,可以信任。

HTTP2

2015年,HTTP/2 发布。它不叫 HTTP/2.0,是因为标准委员会不打算再发布子版本了,下一个新版本将是 HTTP/3。

2022_03_20_http1.1:http2

HTTP2将具有以下几个主要特点:

  • 二进制协议

    HTTP/1.1 版的头信息肯定是文本(ASCII编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧"(frame):头信息帧和数据帧。

  • 多路复用

    HTTP/2 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了"队头堵塞"。

    2022_03_21_fOUqsE

    多路复用来看:虽然http1.1支持了pipelining,但是仍然会有队首阻塞问题,如果浏览器同时发出http请求请求和css,服务器端处理css请求耗时20ms,但是因为先请求资源是html,此时的css尽管已经处理好了但仍不能返回,而需要等待html处理好一起返回,此时的客户端就处于盲等状态,而事实上如果服务器先处理好css就先返回css的话,浏览器就可以开始解析css了。而多路复用的出现就解决了http之前版本协议的问题,极大的提升了页面性能。缩短了通信时间。

    我们来看看有了多路复用之后有那些影响:

    • 无需进行资源分片

      为了避免请求tcp连接耗时长的和初始发送速率低的问题,浏览器允许同时打开多个tcp连接让资源同时请求。但是为了避免服务器压力,一般针对一个域名会有最大并发数的限制,一般来说是6个。允许一个页面同时对相同域名打开6个tcp连接。

      为了绕过最大并发数的限制,会将资源分布在不同的域名下,避免资源在超过并发数后需要等待才能开始请求。而有了http2,可以同步请求资源,资源分片这种方式就可以不再使用。

    • 资源合并

      资源合并会不利于缓存机制,因为单文件修改会影响整个资源包。而且单文件过大对于 HTTP/2 的传输不好,尽量做到细粒化更有利于 HTTP/2 传输。

      而且内置资源也是同理,将资源以base64的形式放进代码中不利于缓存。且编码后的图片资源大小是要超过图片大小的。

      这两者都是以减少tcp请求次数增大单个文件大小来进行优化的。

  • 数据流

    因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的回应。因此,必须要对数据包做标记,指出它属于哪个回应。 HTTP/2 将每个请求或回应的所有数据包,称为一个数据流(stream)

    每个数据流都有一个独一无二的编号。数据包发送的时候,都必须标记数据流ID,用来区分它属于哪个数据流。另外还规定,客户端发出的数据流,ID一律为奇数,服务器发出的,ID为偶数。

    数据流发送到一半的时候,客户端和服务器都可以发送信号(RST_STREAM帧),取消这个数据流。1.1版取消数据流的唯一方法,就是关闭TCP连接。然而HTTP/2 可以取消某一次请求,同时保证TCP连接还打开着,可以被其他请求使用。

    客户端还可以指定数据流的优先级。优先级越高,服务器就会越早回应。

  • 头信息压缩

    HTTP 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如Cookie和User Agent,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。

    HTTP2对这一点做了优化,引入了头信息压缩机制(header compression)

    一方面,头信息使用gzipcompress压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高了速度。

    • 首部表在 HTTP/2 的连接存续期内始终存在,由客户端和服务器共同渐进地更新;
    • 每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值

    头部压缩来看:HTTP/1.1 版的头信息是ASCII编码,也就是不经过压缩的,当我们请求只携带少量数据时,http头部可能要比载荷要大许多,尤其是有了很长的cookie之后这一点尤为显著,头部压缩毫无疑问可以对性能有很大提升。

    2022_03_21_0UJRgE

  • 服务器推送

    HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送(server push)。

    可以想象以下情况,某些资源客户端是一定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间。当然在浏览器兼容的情况下你也可以使用 prefetch。 例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 时再发送这些请求。

    常见场景是客户端请求一个网页,这个网页里面包含很多静态资源。正常情况下,客户端必须收到网页后,解析HTML源码,发现有静态资源,再发出静态资源请求。其实,服务器可以预期到客户端请求网页后,很可能会再请求静态资源,所以就主动把这些静态资源随着网页一起发给客户端了。

    服务器推送来看:少去了资源请求的时间,服务端可以将可能用到的资源推送给服务端以待使用。这项能力几乎是革新了之前应答模式的认知,对性能提升也有巨大帮助。

    2022_03_21_FUbSj9

问题:

HTTP/2创建在传输控制协议open in new window(TCP)上,如果任何一个TCP数据包延迟或丢失,所有多路数据流都会遭受队头阻塞open in new window延迟。

HTTP3

2022_03_20_wm8oeW

虽然 HTTP/2 解决了很多之前旧版本的问题,但是它还是存在一个巨大的问题,主要是底层支撑的 TCP 协议造成的。

上文提到 HTTP/2 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况,那就会导致 HTTP/2 的表现情况反倒不如 HTTP/1 了。

因为在出现丢包的情况下,整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。但是对于 HTTP/1.1 来说,可以开启多个 TCP 连接,出现这种情况反到只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。

那么可能就会有人考虑到去修改 TCP 协议,其实这已经是一件不可能完成的任务了。因为 TCP 存在的时间实在太长,已经充斥在各种设备中,并且这个协议是由操作系统实现的,更新起来不大现实。

基于这个原因,Google 就更起炉灶搞了一个基于 UDP 协议的 QUIC 协议,并且使用在了 HTTP/3 上,HTTP/3 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC(quick UDP Internet Connections)。

QUIC

QUIC(quick UDP Internet Connections)快速 UDP 互联网连接,非常类似于在 UDP 上实现的 TCP + TLS + HTTP/2。由于 TCP 是在操作系统内核和中间件固件中实现的,因此对 TCP 进行重大更改几乎是不可能的。但是,由于 QUIC 建立在 UDP 之上,因此没有这种限制。QUIC 可以实现可靠传输,而且相比于 TCP,它的流控功能在用户空间而不在内核空间,那么使用者就 不受限于 CUBIC 或是 BBR,而是可以自由选择,甚至根据应用场景自由调整优化。

QUIC 与现有 TCP + TLS + HTTP/2 方案相比,有以下几点主要特征:

  • 利用缓存,显著减少连接建立时间
  • 改善拥塞控制,拥塞控制从内核空间到用户空间
  • 没有 head of line 阻塞的多路复用
  • 向前纠错,减少重传
  • 连接平滑迁移,网络状态的变更不会影响连接断线。

2022_03_21_j61w2e

QUIC 在 UDP 之上,如果想要和 TCP/IP 体系类比,那么就是上图。QUIC 可以类比 TCP/IP 中的 TLS 一层。但是功能又不完全是 TLS ,还有一部分 HTTP/2 ,下面还包括一部分 TCP 的功能,比如 拥塞控制、丢包恢复、流量控制等特性。

功能

  • 0-RTT

RTT(Round-Trip Time):往返时延。是指数据从网络一端传到另一端所需的时间。

通过使用类似 TCP 快速打开的技术,缓存当前会话的上下文,在下次恢复会话的时候,只需要将之前的缓存传递给服务端验证通过就可以进行传输了。0RTT 建连可以说是 QUIC 相比 HTTP2 最大的性能优势。那什么是 0RTT 建连呢?

这里面有两层含义:

  • 传输层 0RTT 就能建立连接。
  • 加密层 0RTT 就能建立加密连接。

2022_03_21_7kEvqC

上图左边是 HTTPS 的一次完全握手的建连过程,需要 3 个 RTT。就算是会话复用也需要至少 2 个 RTT。

而 QUIC 呢?由于建立在 UDP 的基础上,同时又实现了 0RTT 的安全握手,所以在大部分情况下,只需要 0 个 RTT 就能实现数据发送,在实现前向加密的基础上,并且 0RTT 的成功率相比 TLS 的会话记录单要高很多。

  • 多路复用(解决队头阻塞Head-of-line Blocking)

虽然 HTTP/2 支持了多路复用,但是 TCP 协议终究是没有这个功能的。QUIC 原生就实现了这个功能,并且传输的单个数据流可以保证有序交付且不会影响其他的数据流,这样的技术就解决了之前 TCP 存在的问题。

同 HTTP2.0 一样,同一条 QUIC 连接上可以创建多个 stream,来发送多个 HTTP 请求,但是,QUIC 是基于 UDP 的,一个连接上的多个 stream 之间没有依赖。比如下图中 stream2 丢了一个 UDP 包,不会影响后面跟着 Stream3 和 Stream4,不存在 TCP 队头阻塞。虽然 stream2 的那个包需要重新传,但是 stream3、stream4 的包无需等待,就可以发给用户。

2022_03_21_ZV98qz

  • 加密认证的报文

TCP 协议头部没有经过任何加密和认证,所以在传输过程中很容易被中间网络设备篡改,注入和窃听。比如修改序列号、滑动窗口。这些行为有可能是出于性能优化,也有可能是主动攻击。

但是 QUIC 的 packet 可以说是武装到了牙齿。除了个别报文比如 PUBLIC_RESET 和 CHLO,所有报文头部都是经过认证的,报文 Body 都是经过加密的。

这样只要对 QUIC 报文任何修改,接收端都能够及时发现,有效地降低了安全风险。

2022_03_21_iokVYk

如上图所示,红色部分是 Stream Frame 的报文头部,有认证。绿色部分是报文内容,全部经过加密。

  • 向前纠错机制

QUIC 协议有一个非常独特的特性,称为向前纠错 (Forward Error Correction,FEC),每个数据包除了它本身的内容之外,还包括了部分其他数据包的数据,因此少量的丢包可以通过其他包的冗余数据直接组装而无需重传。向前纠错牺牲了每个数据包可以发送数据的上限,但是减少了因为丢包导致的数据重传,因为数据重传将会消耗更多的时间(包括确认数据包丢失、请求重传、等待新数据包等步骤的时间消耗)

假如说这次我要发送三个包,那么协议会算出这三个包的异或值并单独发出一个校验包,也就是总共发出了四个包。当出现其中的非校验包丢包的情况时,可以通过另外三个包计算出丢失的数据包的内容。当然这种技术只能使用在丢失一个包的情况下,如果出现丢失多个包就不能使用纠错机制了,只能使用重传的方式了

优点

**1、精细流量控制。**主要依赖于三种关键幀能达到流量控制,一是 window_update 帧,告诉对端自己“可以且最多可以”接收的字节数绝对偏移量;二是blocked 帧,告诉对端数据发送,由于流量控制被阻塞,暂时无法发送;三是stop_waiting 帧,用于通知对端,它不应该继续等待包小于特定值的包。对于直播而言,在直播发起时,在转码跟不上的时候,告诉服务端停止发送直播流或者做出一些应急处理,推动定制的幀去做,保证服务可用性。

**2、无队头阻塞的多路复用。**QUIC 的多路复用,在一条 QUIC 连接上可以发送多个请求 (stream),一个连接上的多个请求(stream)之间是没有依赖的。比如说这个packet丢失,不会影响其他的stream。这个特性对于直播来说是,在弱网下的推流可以保证流畅。

**3、ORTT连接。**QUIC的连接将版本协商、加密、和传输握手交织在一起以减少连接建立延迟。这个特性对于直播来说,使首幀更快,延迟更小。

**4、连接迁移。**传统NAT遇到的问题,比如小区运营商切换端口,导致设备端判断不了新的连接标识,需要重联。而QUIC使用公共包头和连接ID,可以在网络切换的时候不重连,从室内到室外,在理论上可以做到连接不断网。

**5、加密认证的报文。**QUIC把TLS(1.3)等效加密,几乎每个UDP包都加密,报文body都经过加密,从头到脚几乎无死角,对证书也有一些压缩优化,每一个加密包独立认证。这个特性对直播来说,在客户端的防盗链、盗播、劫持上是有好处的。

**6、向前纠错。**QUIC采用向前纠错(FEC)方案,即每个数据包除了本身的数据以外,会带有其他数据包的部分数据,在少量丢包的情况下,可以使用其他数据包的冗余数据完成数据组装而无需重传,从而提高数据的传输速度。对于直播来说可以减少ARG、减少卡顿、提升秒开成功率。

**7、改进的拥塞控制。**这是QUIC最重要的一个特性,TCP的拥塞控制包含了四个算法:慢启动,拥塞避免,快速重传,快速恢复。QUIC 协议当前默认使用TCP协议的Cubic拥塞控制算法,同时也支持 CubicBytes, Reno, RenoBytes, BBR, PCC 等拥塞控制算法。同时QUIC拥有完善的数据包同步机制,在应用层做了很多网络拥塞控制层面的优化,能有效降低数据丢包率,有助降低复杂网络下的直播卡顿率,提升传输效率,使得推流更流畅。

2022_03_21_9UG7oQ

上次更新:
贡献者: chenzilin