http/0.9到http3.0区别和优化

1,085 阅读13分钟

前言

让我们先来看看一个HTTP请求流程

HTTP/0.9

1991年提出,最初的目的只是为了传输体积很小的HTML文件,因此称为超文本传输协议。特点:

  1. 只有一个请求行如GET /index.html,没有请求头和请求体
  2. 服务器没有返回头信息,仅仅返回数据
  3. 返回文件因为都是HTML格式,所以以ASCII字符流来传输

HTTP/1.0

1994年出现了拨号上网,同年网景推出了浏览器,万维网进入了高速发展的阶段。为支持多种类型的文件下载HTTP/1.0引入了请求头和响应头。引入了状态码、提供了Cache机制来缓存下载过的资源、加入了用户代理字段。

例如HTTP请求头告诉服务器自己希望服务器返回的文件类型、压缩方法、文件的编码方式、语种:

accept: text/html
accept-encoding: gzip, deflate, br
accept-Charset: ISO-8859-1,utf-8
accept-language: zh-CN,zh

其中第一行表示期望服务器返回 html 类型的文件,第二行表示期望服务器可以采用 gzip、deflate 或者 br 其中的一种压缩方式,第三行表示期望返回的文件编码是 UTF-8 或者 ISO-8859-1,第四行是表示期望页面的优先语言是中文。

服务器接收到浏览器发送过来的请求头信息之后,会根据请求头的信息来准备响应数据。不过有时候会有一些意外情况发生,比如浏览器请求的压缩类型是 gzip,但是服务器不支持 gzip,只支持 br 压缩,那么它会通过响应头中的 content-encoding 字段告诉浏览器最终的压缩类型,也就是说最终浏览器需要根据响应头的信息来处理数据。下面是一段响应头的数据信息:

content-encoding: br
content-type: text/html; charset=UTF-8

HTTP/1.1

1.持久连接

HTTP/1.0中每一对请求响应都需要单独的TCP连接,HTTP/1.1增加了持久连接,在一个TCP连接上进行多次请求和响应。

默认是Connection: keep-alive,即开启,设置Connection: close手动关闭。

配合Keep-Alive: timeout=5, max=1000来设定连接时长。其中timeout指定一个空闲连接需要保持打开状态而最小时长(单位:秒)。max指定此次连接的最大请求数。

目前浏览器对于同一域名允许最多同时建立6个TCP持久连接。

http1.0之前

http1.1

2.不成熟的HTTP管线化

持久连接虽然能减少 TCP 的建立和断开次数,但是它需要等待前面的请求返回之后,才能进行下一次请求。如果 TCP 通道中的某个请求因为某些原因没有及时返回,那么就会阻塞后面的所有请求,这就是队头阻塞的问题。

HTTP/1.1 中试图通过管线化的技术来解决队头阻塞的问题。HTTP/1.1 中的管线化是指将多个 HTTP 请求整批提交给服务器的技术,虽然可以整批发送请求,不过服务器依然需要根据请求顺序来回复浏览器的请求。

3.提供虚拟主机的支持

在 HTTP/1.0 中,每个域名绑定了一个唯一的 IP 地址,因此一个服务器只能支持一个域名。但是随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址。

因此,HTTP/1.1 的请求头中增加了 Host 字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。

4.对动态生成的内容提供了完美支持

HTTP/1.0中需要在响应头中设置完整的数据大小如Content-Length: 901,以便浏览器根据数据大小准确接收数据。但对于动态生成的内容,传输前不知道最终大小,导致浏览器无法正确接收所有数据。

HTTP/1.1引入了Chunk transfer机制,服务器将数据分割成若干大小的数据块,每个数据块发送时都会附上数据块的长度,最后使用一个零长度的块来结束。这样就可以支持动态内容了。

5.客户端Cookie、安全机制

客户端Cookie机制

用户登录时,服务器验证用户登录信息正确后,会生成一段表示用户身份的字符串,并写入响应头Set-Cookie字段里,然后发送给浏览器,如Set-Cookie: UID=3431uad;。

浏览器将Set-Cookie字段中的值保存到本地,当用户再次访问服务器时,浏览器会读取之前保存的Cookie数据并写入请求头的Cookie字段中,如Cookie: UID=3431uad;。

服务器根据Cookie字段中的值查找该用户的信息,判断是否已登录,然后生成包含该用户信息的页面数据,返回给浏览器。

Cookie安全机制

HttpOnly:Set-Cookie: id=a3fWa; HttpOnly。禁止JavaScript通过document.cookie访问cookie,以阻止XSS攻击。

SameSite: Set-Cookie: id=a3fWa; SameSite=Strict。SameSite有三个值:

  • None: 浏览器会在同站请求、跨站请求下继续发送cookies,不区分大小写。
  • Strict:浏览器只在访问相同站点时发送cookie。
  • Lax:新版浏览器的默认选项,在跨站点的情况下,只允许从第三方站点通过链接打开或get请求携带Cookie,通过post或img、iframe等标签加载的url不会携带cookie。

HTTP1.1存在问题

我们知道 HTTP/1.1 为网络效率做了大量的优化,最核心的有如下三种方式:

  1. 增加了持久连接;
  2. 浏览器为每个域名最多同时维护 6 个 TCP 持久连接;
  3. 使用 CDN 的实现域名分片机制。

但是HTTP1.1还是存在很多问题,如HTTP/1.1对带宽的利用率却并不理想

带宽是指每秒最大能发送或者接收的字节数。我们把每秒能发送的最大字节数称为上行带宽,每秒能够接收的最大字节数称为下行带宽。

第一个原因,TCP 的慢启动。

一旦一个 TCP 连接建立之后,就进入了发送数据状态,刚开始 TCP 协议会采用一个非常慢的速度去发送数据,然后慢慢加快发送数据的速度,直到发送数据的速度达到一个理想状态,我们把这个过程称为慢启动。

你可以把每个 TCP 发送数据的过程看成是一辆车的启动过程,当刚进入公路时,会有从 0 到一个稳定速度的提速过程,TCP 的慢启动就类似于该过程。

慢启动是 TCP 为了减少网络拥塞的一种策略,我们是没有办法改变的。

而之所以说慢启动会带来性能问题,是因为页面中常用的一些关键资源文件本来就不大,如 HTML 文件、CSS 文件和 JavaScript 文件,通常这些文件在 TCP 连接建立好之后就要发起请求的,但这个过程是慢启动,所以耗费的时间比正常的时间要多很多,这样就推迟了宝贵的首次渲染页面的时长了。

第二个原因,同时开启了多条 TCP 连接,那么这些连接会竞争固定的带宽。

你可以想象一下,系统同时建立了多条 TCP 连接,当带宽充足时,每条连接发送或者接收速度会慢慢向上增加;而一旦带宽不足时,这些 TCP 连接又会减慢发送或者接收的速度。比如一个页面有 200 个文件,使用了 3 个 CDN,那么加载该网页的时候就需要建立 6 * 3,也就是 18 个 TCP 连接来下载资源;在下载过程中,当发现带宽不足的时候,各个 TCP 连接就需要动态减慢接收数据的速度。

这样就会出现一个问题,因为有的 TCP 连接下载的是一些关键资源,如 CSS 文件、JavaScript 文件等,而有的 TCP 连接下载的是图片、视频等普通的资源文件,但是多条 TCP 连接之间又不能协商让哪些关键资源优先下载,这样就有可能影响那些关键资源的下载速度了。

第三个原因,HTTP/1.1 队头阻塞的问题。

我们知道在 HTTP/1.1 中使用持久连接时,虽然能公用一个 TCP 管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。这意味着我们不能随意在一个管道中发送请求和接收内容。

这是一个很严重的问题,因为阻塞请求的因素有很多,并且都是一些不确定性的因素,假如有的请求被阻塞了 5 秒,那么后续排队的请求都要延迟等待 5 秒,在这个等待的过程中,带宽、CPU 都被白白浪费了。

在浏览器处理生成页面的过程中,是非常希望能提前接收到数据的,这样就可以对这些数据做预处理操作,比如提前接收到了图片,那么就可以提前进行编解码操作,等到需要使用该图片的时候,就可以直接给出处理后的数据了,这样能让用户感受到整体速度的提升。

但队头阻塞使得这些数据不能并行请求,所以队头阻塞是很不利于浏览器优化的。

HTTP/2.0

前面我们分析了 HTTP/1.1 所存在的一些主要问题:慢启动和 TCP 连接之间相互竞争带宽是由于 TCP 本身的机制导致的,而队头阻塞是由于 HTTP/1.1 的机制导致的。

1.多路复用

2015年5月正式发布HTTP/2协议规范,该协议使用多路复用机制,实现一个域名只使用一个TCP长连接,并消除了队头阻塞问题。多路复用技术能充分利用带宽,最大限度规避了TCP慢启动所带来的问题,使得页面资源的传输速度得到了大幅提升。 使用HTTP/2能带来20% ~ 60%的效率提升。

HTTP/2添加了一个二进制分帧层,将经过的请求转换为一个个带有请求ID编号的帧,服务器接收到所有帧之后,将所有相同ID的帧合并为一条完整的请求信息,处理完请求后,将响应也同样用二进制分帧层转换为一个个带有请求ID编号的帧,浏览器接收到响应帧后根据ID编号将数据提交给对应的请求。

浏览器可以随时发送请求,而不必等待前一个请求接收到响应之后;同样,服务器也可以按需决定优先返回哪些内容,而不必在意顺序,因为每份数据都有ID来标识。这样就实现了资源的并行传输。

2.设置请求优先级

服务器不需要按顺序处理请求,因此对于一些优先级比较高的请求,如关键资源的加载,可以在发送请求时标注优先级,服务器接收到请求后,会优先处理优先级高的请求。

3.服务器推送

服务器可以将数据提前推送到浏览器,例如:用户请求首页HTML文件后,服务器知道该页面会引用几个重要的JavaScript文件和CSS文件,于是可以附带将这些文件一并发送给浏览器,加快渲染速度。

4.头部压缩

头部压缩:HTTP/2对请求头和响应头进行了压缩,在一些大量发送请求体比较少的请求的情况下,传输效率会得到很大的提升。

HTTP2.0对首阻塞问题

虽然 HTTP/2 解决了 HTTP/1.1 中的队头阻塞问题,但是 HTTP/2 依然是基于 TCP 协议的,而 TCP 协议依然存在数据包级别的队头阻塞问题,那么你觉得 TCP 的队头阻塞是如何影响到 HTTP/2 性能的呢?

TCP的问题
  • 队头阻塞:TCP传输的数据被拆分成一个个按顺序排列的数据包,接收端收到后再按顺序将这些数据包组成原始数据。如果在传输过程中,有一个数据包因为网络故障或其它原因丢失,之后的数据就得等待丢失的数据包被重新传输过来,造成了阻塞。而HTTP/2只有一个连接,其他所有请求都会被阻塞。随着丢包率的增加,HTTP/2的传输效率也越来越差。

  • 建立连接的延时:TCP建立连接时的三次握手,如果是HTTPS还有TLS连接握手,整体就需要3~4个RTT(TCP1.5个,TLS根据1.2/1.3版本会有不同的表现)。

  • 协议僵化:互联网需要很多中间设备来讲多个网络互联,这些中间设备包括路由器、防火墙、NAT、交换机等。它们通常依赖一些很少升级的软件,这些软件使用了大量TCP特性,即便客户端升级了TCP协议,新协议的数据经过这些中间设备时,也可能会因为不被理解而丢弃。此外,TCP协议都是通过操作系统内核来实现的,通常操作系统的更新都滞后于软件的更新,也是导致TCP协议僵化的一个原因。

HTTP/3.0

QUIC协议

HTTP/3基于UDP协议实现了类似于TCP的多路复用数据流、传输可靠性等功能,这套功能被称为QUIC协议。

  1. 流量控制、传输可靠性功能:QUIC在UDP的基础上增加了一层来保证数据传输可靠性,它提供了数据包重传、拥塞控制、以及其他一些TCP中的特性。
  2. TLS加密功能:目前QUIC使用TLS1.3,减少了握手所花费的RTT数。
  3. 多路复用:同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输,解决了TCP的队头阻塞问题。
  4. 快速握手:由于基于UDP,可以实现使用0 ~ 1个RTT来建立连接。

HTTP/3.0的挑战

  1. 务器和浏览器端都没有对HTTP/3提供比较完整的支持。
  2. 题,系统内核对UDP的优化远达不到TCP的优化程度。
  3. 备僵化问题,这些设备对UDP的优化程度远低于TCP,据统计使用QUIC协议时,大约有3% ~ 7%的丢包率。