什么是 HTTP2

要说清 HTTP2 的概念我们必须先了解下 HTTP 协议、 HTTP 1.0、 HTTP 1.1

HTTP协议

HTTP(超文本传输协议,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议 HTTP 定义在 TCP 七层协议中的应用层,TCP 解决的是传输层的逻辑 HTTP 客户端发起一个请求,建立一个到服务器指定端口(默认是80端口)的TCP连接 HTTP 连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据

HTTP1.0

HTTP 协议老的标准是HTTP/1.0,为了提高系统的效率,HTTP 1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求。

HTTP1.0 存在的问题

  • 连接无法复用,导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大
  • Head-of-line blocking,会导致带宽无法被充分利用,以及后续健康请求被阻塞

HTTP1.1

HTTP 1.1在继承了HTTP 1.0优点的基础上,也克服了HTTP 1.0的性能问题。HTTP 1.1通过增加更多的请求头和响应头来改进和扩充HTTP 1.0的功能。

HTTP/1.1相较于 HTTP/1.0 协议的区别

  • 缓存处理
  • 带宽优化及网络连接的使用
  • 错误通知的管理
  • 消息在网络中的发送
  • 互联网地址的维护
  • 安全性及完整性

HTTP1.1 存在的问题

  • HTTP 1.1 包含了太多细节和可选的部分,这让它变得过于庞大
  • HTTP 1.1很难榨干TCP协议所能提供的所有性能,HTTP客户端和浏览器必须要另辟蹊径的去找到新的解决方案来降低页面载入时间
  • 传输大小和资源数量
  • Head-of-line blocking 一个浏览器默认针对同一个域名的网站默认只会打开 6-8 个 TCP 连接,并且一个 TCP 连接一次只能处理一个请求的发送,因此当一个页面有非常多的请求需要发送的时候,必须在建立 6-8 个 TCP 连接中找一个排队等待发送请求。虽然针对 Head-of-line blocking 有 HTTP pipelining 这样一种技术:在等待上一个请求响应的同时,发送下一个请求。但是由于 HTTP 1.1 的是使用字符串来传输数据的,因此区分返回的数据是属于那个请求有极大的障碍。即使到了今天,大部分桌面浏览器仍然会选择默认关闭HTTP pipelining这一功能的原因。

HTTP1.1 常见的优化手段

  • Spriting 将很多较小的图片合并成一张大图,再用JavaScript或者CSS将小图重新“切割”出来的技术,将多个请求合并在一个请求中返回
  • 内联(Inlining)将图片的原始数据嵌入在CSS文件里面的URL里,例如 background: url(data:image/png;base64,<data>) no-repeat;
  • 拼接(Concatenation)利用一些前端工具将这些文件合并为一个大的文件,从而让浏览器能只花费一个请求就将其下载完,而不是发无数请求去分别下载那些琐碎的JavaScript文件。这也会有一个问题,就是有个小的改动需要全量下载整个文件
  • 分片(Sharding)把你的服务分散在尽可能多的主机上,规避浏览器同一个域名只能同时有 6-8 个 TCP 连接建立

HTTP 2.0

二进制传输

基于二进制的 http2 可以使成帧的使用变得更为便捷。在HTTP1.1和其他基于文本的协议中,对帧的起始和结束识别起来相当复杂。而通过移除掉可选的空白符以及其他冗余后,再来实现这些会变得更容易。

二进制格式

http2会发送有着不同类型的二进制帧,但他们都有如下的公共字段:Type, Length, Flags, Stream Identifier和frame payload

多路复用的流

Stream Identifier将http2连接上传输的每个帧都关联到一个“流”。流是一个独立的,双向的帧序列可以通过一个http2的连接在服务端与客户端之间不断的交换数据。

每个单独的http2连接都可以包含多个并发的流,这些流中交错的包含着来自两端的帧。流既可以被客户端/服务器端单方面的建立和使用,也可以被双方共享,或者被任意一边关闭。在流里面,每一帧发送的顺序非常关键。接收方会按照收到帧的顺序来进行处理。

流的多路复用意味着在同一连接中来自各个流的数据包会被混合在一起。就好像两个(或者更多)独立的“数据列车”被拼凑到了一辆列车上,但它们最终会在终点站被分开。

起点:多个数据 列车1: A1 -> A2 -> A3 列车2: B1 -> B2 -> B3

传输过程中:拼接传输 示例流传输: A1 -> B1 -> A2 -> B2 -> A3 -> B3

终点:将传输过来的额数据重新分流 列车1: A1 -> A2 -> A3 列车2: B1 -> B2 -> B3

优先级和依赖性

每个流都包含一个优先级(也就是“权重”),它被用来告诉对端哪个流更重要。当资源有限的时候,服务器会根据优先级来选择应该先发送哪些流。

借助于PRIORITY帧,客户端同样可以告知服务器当前的流依赖于其他哪个流。该功能让客户端能建立一个优先级“树”,所有“子流”会依赖于“父流”的传输完成情况。

优先级和依赖关系可以在传输过程中被动态的改变。这样当用户滚动一个全是图片的页面的时候,浏览器就能够指定哪个图片拥有更高的优先级。或者是在你切换标签页的时候,浏览器可以提升新切换到页面所包含流的优先级。

头压缩

HTTP是一种无状态的协议。简而言之,这意味着每个请求必须要携带服务器需要的所有细节,而不是让服务器保存住之前请求的元数据。因为http2并没有改变这个范式,所以它也以同样原理工作。

这也保证了HTTP可重复性。当一个客户端从同一服务器请求了大量资源(例如页面的图片)的时候,所有这些请求看起来几乎都是一致的,而这些大量一致的东西则正好值得被压缩。

当每个页面资源的个数上升的时候,cookies和请求的大小都会增加。cookies需要被包含在所有请求中,且他们在多个请求中经常是一模一样的。

HTTP 1.1请求的大小正变得越来越大,有时甚至会大于TCP窗口的初始大小,这会严重拖累发送请求的速度。因为它们需要等待带着ACK的响应回来以后,才能继续被发送。这也是另一个需要压缩的理由。

重置

HTTP 1.1的有一个缺点是:当一个含有确切值的Content-Length的HTTP消息被送出之后,你就很难中断它了。当然,通常你可以断开整个TCP链接(但也不总是可以这样),但这样导致的代价就是需要通过三次握手来重新建立一个新的TCP连接。

一个更好的方案是只终止当前传输的消息并重新发送一个新的。在http2里面,我们可以通过发送RST_STREAM帧来实现这种需求,从而避免浪费带宽和中断已有的连接。

服务器推送

这个功能通常被称作“缓存推送”。主要的思想是:当一个客户端请求资源X,而服务器知道它很可能也需要资源Z的情况下,服务器可以在客户端发送请求前,主动将资源Z推送给客户端。这个功能帮助客户端将Z放进缓存以备将来之需。

服务器推送需要客户端显式的允许服务器提供该功能。但即使如此,客户端依然能自主选择是否需要中断该推送的流。如果不需要的话,客户端可以通过发送一个RST_STREAM帧来中止。

流量控制

每个http2流都拥有自己的公示的流量窗口,它可以限制另一端发送数据。如果你正好知道SSH的工作原理的话,这两者非常相似。

对于每个流来说,两端都必须告诉对方自己还有足够的空间来处理新的数据,而在该窗口被扩大前,另一端只被允许发送这么多数据。

而只有数据帧会受到流量控制。

如何搭建一个支持 HTTP2 的服务

  • 安装 nginx,版本大于 1.9.5
  • 开启 --with-http_v2_module
  • 开启 https,因为现在所有的浏览器实现的 HTTP2 都是基于 TLS 实现的。可以使用 Let's Encrypt 为网站生成 TLS 证书
  • 修改 nginx 配置,开启 http2 模块
server {
  listen 443 http2 ssl;
  listen [::]:443 http2 ssl ipv6only=on;
  server_name youdomain.cc;
  ssl_certificate /etc/letsencrypt/live/youdomain.cc/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/youdomain.cc/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/youdomain.cc/chain.pem;
}

参考文献:

《http2讲解》 如何优雅的谈论HTTP/1.0/1.1/2.0

results matching ""

    No results matching ""