我们不能失去信仰

我们在这个世界上不停地奔跑...

0%

从过去到未来 关于 HTTP2 的研究

HTTP2简介

先说一下 HTTP 的发展简史,以及 HTTP/2.0 的优势在哪里。

image-20180925163449365

HTTP/0.9

1991年发布的 HTTP/0.9 版本 是基于 TCP/IP 协议的应用层协议。它不涉及数据包的传输,只规定了客户端和服务器之间的通信格式,默认使用80端口,该版本极其简单,只有一个命令 GET , 并且只支持文本传输。

请求过程: TCP 建立后,客户端向服务器请求 网页 如 index.html , 并且协议规定, 服务器只能回应 HTML 格式的字符串, 不能回应别的格式。 服务器发送完毕后, 就关闭连接。

HTTP/1.0

1996年5月, HTTP/1.0 版本发布。

首先: 加入了很多格式的内容都可以发送,这使得互联网不仅可以传输文字,还能传输图像、视频、二进制文件。 通过这次的更新,为互联网的大发展奠定了基础。

其次: 除了GET命令,还加入了 POST 、HEAD 等命令, 丰富了浏览器与服务器的交互手段。

最后: HTTP 请求和回应的格式也变了。 除了数据部分,每次通信都必须包括头信息(HTTP header),用来描述一些元数据。

其他的新增功能还包括状态码、多字符集支持、多部分发送、权限、缓存、内容编码 等

相比 HTTP/0.9 有巨大的变化。

但是, HTTP/1.0 还是存在一些缺点,其中主要缺点是,每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接。

TCP 新建连接的成本很高,因为需要客户端和服务器三次握手,并且开始时发送速率较慢,因为 TCP 通过慢开始启动。 所以,HTTP/1.0 版本的性能就比较差。 随着网页加载的外部资源越来越多,这个问题就愈发突出了。

解决每次都要重新建立连接的方法是: 在客户端和服务器端各自都加了一个 Connection: keep-alive 以便请求复用。但是,这个字段不是标准字段, 不同实现的行为可能不一致, 因此不是根本的解决方法。

HTTP/1.1

image-20180925154917714

1997年1月, HTTP/1.1 版本发布, 只比 1.0 版本晚了半年。 它进一步完善了 HTTP 协议,一直用到了20年后的今天, 直到现在还是最流行的版本。

加入了一些新的功能如:

  • 持久连接

    Connection: keep-alive , 即 TCP 连接时默认不关闭, 可以被多个请求复用,不用声明 Connection: keep-alive。

客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。规范的做法是,客户端在最后一个请求时,发送 Connection: close , 明确要求服务器关闭 TCP 连接。 Connection: close

目前,为了安全性,对于同一个域名,大多数浏览器允许同时建立6个持久连接。

管线化:即在同一个 TCP 连接里面,客户端可以同时发送多个请求, 这样就进一步改进了 HTTP 协议的效率。(虽然可以提高效率,但是支持的很少,用的也不多,确实会提升一些效率,但是并且也存在一些问题,后面HTTP/2.0再说)

  • Content-Length 字段

    一个 TCP 连接同时可以传送多个回应,那么就需要有一种机制,区分数据包是属于哪一个回应的。这就是 Content-Length 字段的作用, 声明本次回应的数据长度。

    如: Content-Length: 3000

    他告诉浏览器,本次回应的长度是 3000 字节, 后面的字节就属于下一个回应了。

    在 HTTP/1.0 版本中, Content-Length 字段不是必须的,因为不涉及一次传多个回应,或者浏览器发现服务器关闭了 TCP连接,就表明收到的数据包已经全了。

  • 分块传输编码

    使用Content-Length 字段的前提条件是, 服务器发送回应之前,必须知道响应的数据包长度。

    对于一些很耗时的动态操作来说,这意味着服务器需要等所有操作完成,才能发送数据,显然这样的效率不高。更好的处理方法是,产生一块数据,就发送一块,采用“流模式”(stream) 取代 “缓存模式”(buffer)。

    因此,HTTP/1.1 版本规定可以不使用 Content-Length 字段,而使用 “分块传输编码”(chunked transfer encoding)。只要请求或回应的头信息有 Transfer-Encoding 字段, 就表明回应将由数量未定的数据块组成。

    每个非空的数据块之前,会有一个16进制的数值,表示这个块的长度。最后是一个大小为 0 的块, 就表示本次回应的数据发送完了。

  • 其他功能

    HTTP/1.1 版本还增加了很多动词方法如:PUT、PATCH、HEAD、OPTIONS、DELETE

    另外,客户端请求的头信息新增了 Host 字段,用来指定服务器的域名。

    有了Host字段,就可以将请求发往同一台服务器上的不同网站,为虚拟主机的兴起打下了基础。

  • 缺点

    • 线头阻塞

      虽然加入了持久连接,管线化,但是同一个 TCP 连接里面,所有的数据通信都是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。如果前面的回应特别慢,后面就会有很多请求排队等着。这称为“队头堵塞”(Head-line blocking)

      为了避免这个问题,有两种方法:一是减少请求数量,二是同时多开持久连接。这导致了很多的网页优化技巧,比如合并脚本和样式表,将图片嵌入CSS代码、域名分片等等。

    • 为了解决此类问题的一些解决方法:

      • Spriting是将众多个小图片整合成大图片,然后再拆分,坏处可想而知,每个网页需不需要用到那么多图片,都需要发送。
      • 内联(Inlining),简而言之就是把图片资源放在 CSS 文件里面的 URL 里面,这种缺点和Spriting类似。
      • 拼接(Concatenation),如果 js 文件特别多的话,可以使用工具把众多 js 文件整合成一个文件,这样浏览器就只需要下载一次就可以完成,而不是无数次请求去下载,但是如果 js 的某个代码被改动,那么也需要重新下载,这样会对调试和开发者造成极大的不变。
      • 分片(Sharding),因为 HTTP/1.1 规范提到一个客户端最多能对同一主机建立有限个 TCP 连接。因此,Sharding 就是把服务分散在尽可能多的服务器上面。这样用户就可以同时和多台主机建立很多 TCP 连接,从而降低载入时间。
    • 过多的可选项和太多的细节

      并且 HTTP/1.1 包含了太多细节和可选的部分,这让它变得过于庞大。

      过多的可选项,这就导致了在一些不常用的功能在后来的实现中很少会被支持,而有些最初实现了的功能,却又很少被使用。

      随着时间的推移,这些当初看似被边缘化的功能逐渐被用上,客户端和服务器的互用性问题就暴露出来了。HTTP 管线化(HTTP pipelining) 就是一个非常好的例子。

    • 未能被充分利用的 TCP

      我们可以通过更好的利用 TCP 来减少传输过程中的暂停,并充分挖掘利用那些本可以用于发送/接收更多数据的时间。

    • 传输大小和资源数量

      近年来加载网站首页需要下载的数据量在逐渐增加,并且已经超过了 1.9MB。并且我们更关心的是平均每个页面为了完成显示与渲染所需要的资源已经超过100个了。

    • 明显的延迟

      HTTP/1.1 在网络延迟方面做的就不尽人意了。部分原因是 HTTP 管线化(pipelining)还存在很多问题,所以对大部分用户来说这项技术还是默认被关闭的。虽然近几年,我们的网络带宽从原来只有 几百KB ,现在一般的网络也能达到 数十MB ,但是 网络延迟也没有降低。比如在移动设备上,即使拥有高的连接速率,也很难获得优质快速的网络体验。

      例如:可以将资源分布在不同的主机上面来建立连接,获得更快的速度。

      image-20180925152722166

SPDY协议

2009年,Google 公开了自行研发的 SPDY 协议, 主要解决 HTTP/1.1 效率不高的问题。

这个协议在 Chrome 浏览器上证明可行以后,就被当做 HTTP/2 的基础, 主要特性都在 HTTP/2 之中得到继承。

如果用 chrome ,就会发现,在百度网站上就有用 SPDY 的例子:

image-20180925152857946

HTTP/2

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

  • 改进

    • 降低协议对延迟的敏感
    • 修复 pipelining 和 head of line blocking 问题
    • 防止主机需要更高的连接数量
    • 保留所有现有的接口,内容,URI 格式和结构
  • HTTP/2 是一个二进制协议

    • 我们可以使用 Wireshark 这样的 http/2 解析器来分析和调试协议。

      image-20180925153526276

    • http/2 会发送有着不同类型的二进制帧,他们有一些公共字段,一共规范了10种不同的帧,其中最基础的两种分别对应于 HTTP/1.1 的 DATA 和 HEADERS。

    • 多路复用的流,实现双向的,实时的通信,多工。

      image-20180925160350878

    • 数据包具有优先级和依赖性,服务器可以优先处理

    • 头部压缩,可以降低传输成本,但是有可能会存在安全问题。

    • 支持 重置 ,就是如果要传输一个东西,但是需要传输好几次,然后传输了一半之后,接收方突然不想要了,这个时候可以发送一个 RST_STREAM 帧来终止。而在 HTTP/1.1 时,则需要断开整个连接。

  • 服务器主动推送,也可以称缓存推送

    • 如果客户端请求一个资源,服务器可以推断出来客户端 请求完此资源后,会再请求的资源,这个时候服务器就可以主动准备好资源并且一并推送给客户端。如果客户端不需要,也可以发送一个RST_STREAM 帧来终止。

    • 流量控制: HTTP/2 可以有自己公示的流量窗口,它可以限制一端发送数据。和 SSH 的工作原理类似。

      image-20180925160508950

其中包含了很多信息,其中有 Stream ID SEQ/ACK 等 字段

下面这张图来说明 HTTP/1.1 和 HTTP/2 在性能方面的差别。

image-20180925155506937