HTTP的前世今生(二)
HTTP/2
HTTP/1.1 发布之后,整个互联网世界呈现出了爆发式的增长,度过了十多年的“快乐时光”,更涌现出了 Facebook、Twitter、淘宝、京东等互联网新贵。
这期间也出现了一些对 HTTP 不满的意见,主要就是连接慢,无法跟上迅猛发展的互联网,但 HTTP/1.1 标准一直“岿然不动”,无奈之下人们只好发明各式各样的“小花招”来缓解这些问题,比如以前常见的切图、JS 合并等网页优化手段。
终于有一天,搜索巨头 Google 忍不住了,决定“揭竿而起”,就像马云说的“如果银行不改变,我们就改变银行”。那么,它是怎么“造反”的呢?
Google 首先开发了自己的浏览器 Chrome,然后推出了新的 SPDY 协议,并在 Chrome 里应用于自家的服务器,如同十多年前的网景与微软一样,从实际的用户方来“倒逼”HTTP 协议的变革,这也开启了第二次的“浏览器大战”。
历史再次重演,不过这次的胜利者是 Google,Chrome 目前的全球的占有率超过了 60%。“挟用户以号令天下”,Google 借此顺势把 SPDY 推上了标准的宝座,互联网标准化组织以 SPDY 为基础开始制定新版本的 HTTP 协议,最终在 2015 年发布了 HTTP/2,RFC 编号 7540。
HTTP/2 的制定充分考虑了现今互联网的现状:宽带、移动、不安全,在高度兼容 HTTP/1.1 的同时在性能改善方面做了很大努力,主要的特点有:
多路复用
多路复用主要是为了解决 TCP 慢启动和队头阻塞问题。具体可以参考下图:
从图中你会发现每个请求都有一个对应的 ID,如 stream1 表示 index.html 的请求,stream2 表示 foo.css 的请求。这样在浏览器端,就可以随时将请求发送给服务器了。
服务器端接收到这些请求后,会根据自己的喜好来决定优先返回哪些内容,比如服务器可能早就缓存好了 index.html 和 bar.js 的响应头信息,那么当接收到请求的时候就可以立即把 index.html 和 bar.js 的响应头信息返回给浏览器,然后再将 index.html 和 bar.js 的响应体数据返回给浏览器。之所以可以随意发送,是因为每份数据都有对应的 ID,浏览器接收到之后,会筛选出相同 ID 的内容,将其拼接为完整的 HTTP 响应数据。
HTTP/2 使用了多路复用技术,可以将请求分成一帧一帧的数据去传输,这样带来了一个额外的好处,就是当收到一个优先级高的请求时,比如接收到 JavaScript 或者 CSS 关键资源的请求,服务器可以暂停之前的请求来优先处理关键资源的请求。
多路复用技术的实现原理是添加了一个二进制分帧层。数据经过二进制分帧层处理后,会被转换为一个个带有请求 ID 标号的帧,服务器收到所有帧之后,会将所有相同 ID 的帧合并成一个完整的请求信息。然后服务器已同样的方式将响应数据转换为带请求 ID 的帧,再发送给浏览器。浏览器再组合响应数据。
可以设置请求的优先级
我们知道浏览器中有些数据是非常重要的,但是在发送请求时,重要的请求可能会晚于那些不怎么重要的请求,如果服务器按照请求的顺序来回复数据,那么这个重要的数据就有可能推迟很久才能送达浏览器,这对于用户体验来说是非常不友好的。
为了解决这个问题,HTTP/2 提供了请求优先级,可以在发送请求时,标上该请求的优先级,这样服务器接收到请求之后,会优先处理优先级高的请求。
服务器推送
除了设置请求的优先级外,HTTP/2 还可以直接将数据提前推送到浏览器。你可以想象这样一个场景,当用户请求一个 HTML 页面之后,服务器知道该 HTML 页面会引用几个重要的 JavaScript 文件和 CSS 文件,那么在接收到 HTML 请求之后,附带将要使用的 CSS 文件和 JavaScript 文件一并发送给浏览器,这样当浏览器解析完 HTML 文件之后,就能直接拿到需要的 CSS 文件和 JavaScript 文件,这对首次打开页面的速度起到了至关重要的作用。
头部压缩
无论是 HTTP/1.1 还是 HTTP/2,它们都有请求头和响应头,这是浏览器和服务器的通信语言。HTTP/2 对请求头和响应头进行了压缩,你可能觉得一个 HTTP 的头文件没有多大,压不压缩可能关系不大,但你这样想一下,在浏览器发送请求的时候,基本上都是发送 HTTP 请求头,很少有请求体的发送,通常情况下页面也有 100 个左右的资源,如果将这 100 个请求头的数据压缩为原来的 20%,那么传输效率肯定能得到大幅提升。
HTTP/2 协议规范于 2015 年 5 月正式发布,在那之后,该协议已在互联网和万维网上得到了广泛的实现和部署。从目前的情况来看,国内外一些排名靠前的站点基本都实现了 HTTP/2 的部署。使用 HTTP/2 能带来 20%~60% 的效率提升,至于 20% 还是 60% 要看优化的程度。总之,我们也应该与时俱进,放弃 HTTP/1.1 和其性能优化方法,去“拥抱”HTTP/2。
HTTP/3
看到这里,你可能会问了:“HTTP/2 这么好,是不是就已经完美了呢?”答案是否定的,这一次还是 Google,而且它要“革自己的命”。
在 HTTP/2 还处于草案之时,Google 又发明了一个新的协议,叫做 QUIC,而且还是相同的“套路”,继续在 Chrome 和自家服务器里试验着“玩”,依托它的庞大用户量和数据量,持续地推动 QUIC 协议成为互联网上的“既成事实”。
我们再来看看为什么出现了 HTTP/3 ,它的出现是为了解决 HTTP/2 的什么问题。
TCP 的队头阻塞
虽然 HTTP/2 解决了应用层面的队头阻塞问题,不过和 HTTP/1.1 一样,HTTP/2 依然是基于 TCP 协议的,而 TCP 最初就是为了单连接而设计的。你可以把 TCP 连接看成是两台计算机之前的一个虚拟管道,计算机的一端将要传输的数据按照顺序放入管道,最终数据会以相同的顺序出现在管道的另外一头。
接下来我们就来分析下 HTTP/1.1 协议栈中 TCP 是如何传输数据的。为直观理解,你可以参考下图:
通过上图你会发现,从一端发送给另外一端的数据会被拆分为一个个按照顺序排列的数据包,这些数据包通过网络传输到了接收端,接收端再按照顺序将这些数据包组合成原始数据,这样就完成了数据传输。
不过,如果在数据传输的过程中,有一个数据因为网络故障或者其他原因而丢包了,那么整个 TCP 的连接就会处于暂停状态,需要等待丢失的数据包被重新传输过来。你可以把 TCP 连接看成是一个按照顺序传输数据的管道,管道中的任意一个数据丢失了,那之后的数据都需要等待该数据的重新传输。为了直观理解,你可以参考下图:
我们就把在 TCP 传输过程中,由于单个数据包的丢失而造成的阻塞称为 TCP 上的队头阻塞。那队头阻塞是怎么影响 HTTP/2 传输的呢?首先我们来看正常情况下 HTTP/2 是怎么传输多路请求的,为了直观理解,你可以参考下图:
通过该图,我们知道在 HTTP/2 中,多个请求是跑在一个 TCP 管道中的,如果其中任意一路数据流中出现了丢包的情况,那么就会阻塞该 TCP 连接中的所有请求。这不同于 HTTP/1.1,使用 HTTP/1.1 时,浏览器为每个域名开启了 6 个 TCP 连接,如果其中的 1 个 TCP 连接发生了队头阻塞,那么其他的 5 个连接依然可以继续传输数据。
所以随着丢包率的增加,HTTP/2 的传输效率也会越来越差。有测试数据表明,当系统达到了 2% 的丢包率时,HTTP/1.1 的传输效率反而比 HTTP/2 表现得更好。
TCP 协议僵化
现在我们知道了 TCP 协议存在队头阻塞等缺点,那我们是不是可以通过改进 TCP 协议来解决这些问题呢?
答案是:非常困难。之所以这样,主要有两个原因。
第一个是中间设备的僵化。要搞清楚什么是中间设备僵化,我们先要弄明白什么是中间设备。我们知道互联网是由多个网络互联的网状结构,为了能够保障互联网的正常工作,我们需要在互联网的各处搭建各种设备,这些设备就被称为中间设备。
这些中间设备有很多种类型,并且每种设备都有自己的目的,这些设备包括了路由器、防火墙、NAT、交换机等。它们通常依赖一些很少升级的软件,这些软件使用了大量的 TCP 特性,这些功能被设置之后就很少更新了。
所以,如果我们在客户端升级了 TCP 协议,但是当新协议的数据包经过这些中间设备时,它们可能不理解包的内容,于是这些数据就会被丢弃掉。这就是中间设备僵化,它是阻碍 TCP 更新的一大障碍。
除了中间设备僵化外,操作系统也是导致 TCP 协议僵化的另外一个原因。因为 TCP 协议都是通过操作系统内核来实现的,应用程序只能使用不能修改。通常操作系统的更新都滞后于软件的更新,因此要想自由地更新内核中的 TCP 协议也是非常困难的。
QUIC 协议
HTTP/2 存在一些比较严重的与 TCP 协议相关的缺陷,但由于 TCP 协议僵化,我们几乎不可能通过修改 TCP 协议自身来解决这些问题,那么解决问题的思路是绕过 TCP 协议,发明一个 TCP 和 UDP 之外的新的传输协议。但是这也面临着和修改 TCP 一样的挑战,因为中间设备的僵化,这些设备只认 TCP 和 UDP,如果采用了新的协议,新协议在这些设备同样不被很好地支持。
因此,HTTP/3 选择了一个折衷的方法——UDP 协议,基于 UDP 实现了类似于 TCP 的多路数据流、传输可靠性等功能,我们把这套功能称为 QUIC 协议。关于 HTTP/2 和 HTTP/3 协议栈的比较,你可以参考下图:
通过上图我们可以看出,HTTP/3 中的 QUIC 协议集合了以下几点功能。
实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。
集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数。
实现了 HTTP/2 中的多路复用功能。和 TCP 不同,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。实现了数据流的单独传输,就解决了 TCP 中队头阻塞的问题。
实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。
在 2018 年,互联网标准化组织 IETF 提议将“HTTP over QUIC”更名为“HTTP/3”并获得批准,HTTP/3 正式进入了标准化制订阶段,也许几年后就会正式发布。
虽说这套协议解决了 HTTP/2 中因 TCP 而带来的问题,不过由于是改动了底层协议,所以推广起来还会面临着巨大的挑战。