1.internet protocol stack
为了更好的把握和理解HTTP/2,你需要了解抽象网络协议栈模型。
2.HTTTP/2
HTTTP/2始于SPDY协议,主要在Google得以发展,其目的是试图通过压缩,多路复用以及优先级次序等技术来降低网页加载的延迟。
从HTTP/2自2015年5月发布以来,许多浏览器为支持该协议做了积极的努力,包括Chrome,Opera,IE,Safari。正是由于这部分浏览器的支持,从2015年来,该协议有了很高的采用率,尤其是在一些新站点中采用率更高。
从技术角度看,HTTP/1.1与HTTP/2一个最为重大的不同是二进制帧层(binary framing layer),其可以视为网络协议栈中的应用层的一部分。
HTTP/1.1中所有的请求与响应均以普通文本格式(plain text format)来传输的;
HTTP/2使用二进制帧层将http message封装为二进制格式(binary format),同时保持所使用的HTTP语义不变,如:verbs,methods,headers等。
一个应用程序级API依然创建常规格式的http message,但在底层会将这些message转成二进制。这保证了在HTTP/2之前创建的web应用程序在与新协议互动时依然能够正常工作。
将message转换层二进制使得HTTP/2能够尝试一些HTTP/1.1无法实现的新方式来传输数据。
3.Delivery Models
相比于HTTP/1.1通过普通文本格式来传输message,HTTP/2将message编码成二进制格式来传输的方式为许多不同的传输模型提供了可能。
下面来简要看看在HTTP/1.1的传输模型下,它如何改善效率以及它所带来的问题,最后再对比HTTP/2的二进制帧层有什么优势以及它是如何确定请求的优先次序。
HTTP/1.1 — Pipelining and Head-of-Line Blocking
发起一个获取页面的get请求,客户端得到的第一个响应通常不是可以完整渲染出来的页面,其包含了页面渲染所需要的其它资源的链接,客户端在加载完页面后才知道该页面所需要的其它资源有哪些。
继而客户端需要发起更多的请求来获取这些资源,最终才能将页面完整渲染出来。 所以在 HTTP/1.1 中,客户端每创建一个新请求都需要经历TCP的连接与断开, 即一个 http 事务,这对于时间和资源来说都是较大的消耗。
对于该问题,HTTP/1.1的解决方案是persistent connections and pipelining,这个优化策略虽然极大地改善了性能但存在一个天然的瓶颈。
在传输到一个相同的目的地时,由于多个数据包间无法互相传递,所以可能存在这样一种情况,位于队列首位的请求倘若无法取得它所需要的资源,其后面的所有请求都将被阻塞,这就是著名的head-of-line(HOL)blocking,在HTTP/1.1中对于优化连接效率来说这是一个重大问题。
并行TCP连接parallel TCP connection可以减轻这个问题,但客户端与服务器之间并发TCP连接数量存在限制且每个连接都需要大量资源。
HTTP/2 — Advantages of the Binary Framing Layer
二进制帧层对请求/响应消息编码并分割为更小的信息包,这极大地增加了数据传输的灵活性。
HTTP/2在两台机器间只建立一个连接对象,在该连接中存在多个数据流,每个数据流都由多个类似request/response格式的消息构成,最后每个消息将被拆分为更小的单元,称之为frame(帧)。
identify tag),所以在连接中传输时这些帧可以是乱序的且可以在另一端完成重新组装。
乱序的请求和响应可以在不阻塞其后面的消息的前提下并发运行,该过程称为多路复用multiplexing。
多路复用通过保证没有消息需要等待另一个消息完成的方式来解决HTTP/1.1中的head-of-line blocking问题。
多路复用也意味着客户端和服务器可以发送并发请求和响应,从而实现更好的控制和更高效的连接管理。
由于多路复用允许客户端并发地构建多个流,因此这些流仅需要单个TCP连接。这能提供更好的网络和带宽利用率且显著降低操作代价。
此外,单个TCP连接也能改善HTTPS协议的性能,因为客户端和服务器可以在多个请求/响应中复用相同的安全会话(same secured session)。
尽管多路复用能解决HTTP/1.1中的某些问题,但若多个数据流都等待相同但资源时依然会造成性能问题,HTTP/2对于这个问题采用流优先级(stream prioritization)来解决。
HTTP/2 — Stream Prioritization
Stream Prioritization不仅仅用于解决多个流同时等待相同资源造成的性能问题,同时允许开发者自定义请求的相对权重来优化应用性能。
当客户端向服务器并发多个请求时,通过为每个流分配一个 1~256 的权重数来为请求的响应进行优先级排序。权重数越大优先级越高。
此外,客户端还通过指定流所依赖的流的ID来声明每个流对另一流的依赖性。若父识别码(Parent Identity)被省略则认为该流依赖于根流(root stream)。
stream 1 没有关联的PID,故默认其与根节点关联。其余stream都有各自的PID。
每个流的资源分配将基于它们所拥有的权重及其所有的依赖关系。stream 5、6拥有相同的权重和PID,故stream5,6拥有相同的资源分配优先级。
服务器利用这些信息来创建依赖树,依赖树帮助服务器决定请求取得它们所需数据的顺序。基于上图的流关系,依赖树如下:
依据上图,首先,所有可用资源分配给直接关联root的 stream 1,依赖树中表明 stream 2 依赖于 stream 1 的完成,故 stream 2 直到 stream 1 任务完成后才能被处理。
stream 2完成后,stream 3、4 获得所有可用资源,按照stream 3,4的权重比例 2 : 4来分配资源,可见stream 4将获得更大比例的资源。
最后,当stream 3任务完成后,stream 5,6获得可用资源并按权重同等比例分配资源,这有可能发生在stream 4任务完成之前,尽管stream 4相对于stream 3将获取更大比例的资源。
较低级别的stream任务允许被立即执行只要其所依赖的更高级别的stream任务已完成。
作为开发者,你可以根据需要在请求中设定其权重。例如,在提供了缩略图的情况下,你可以为高分辨率的图片加载设定较低的权重。
通过提供权重分配,HTTP/2让开发者能更好的控制页面的渲染,同时还允许客户端修改依赖和在与用户交互的响应过程中在运行时下重新分配权重。还有一点需要注意,如果某个stream访问某个特定资源而被阻塞了,服务器有可能会修改其已分配的优先级。
4.Buffer Overflow
建立TCP连接的双方,如客户端和服务器都有各自特定量的缓冲区来暂存已接收但尚未及时处理的请求。缓冲区为处理大量或特别大的请求以及连接中上下流速度不均匀的问题提供了灵活性。
但存在一种缓冲区不足的情形,比方说服务器可能不断的推送大量客户端应用程序由于缓冲区有限或低带宽而无法处理的数据给客户端,同样地,客户端也可能上传巨大的图片或视频给服务器导致服务器缓冲区溢出进而造成某些数据丢失。
为了避免缓冲区溢出,流控制机制必须防止发送方用数据淹没接收方。接下来让我们看看HTTP/1.1和HTTP/2是如何基于各自不用的传输模型使用该机制的不同版本来处理流控制的。
HTTP/1.1
流控制依赖底层的TCP连接。当初始化连接时,客户端和服务器将按照各自系统的默认设置来创建各自的特定大小的缓冲区。当接收方缓冲区中已填充了数据,它会告知发送者它的接收窗口receive window,如缓冲区剩余可用空间的数量。
接收窗口被携带在一个被称为ACK包的信号中,ACK包是接收方发出的一个数据包用以确认其已接收到数据的信号。若携带的接收窗口的大小是 0 ,发送方则不再发送更多的数据直到客户端清空它的内部缓冲区然后请求恢复数据传输。
需要注意的是,基于底层TCP连接使用的 receive window 只能在连接的其中一端实现流控制。
HTTP/2
HTTP/2是在单个TCP连接中多路复用数据流,因此在TCP连接级别上的receive window对于调节个别流的传输是低效的。HTTP/2通过允许客户端和服务器实现各自的流控制而非依赖于运输层的方式来解决缓冲区溢出问题。
应用程序层传达可用的缓冲区空间,从而允许客户端和服务器在多路复用流的级别上设置接收窗口。初始连接后,可以通过WINDOW_UPDATE帧修改或维护此精细流控制。
由于这种方式控制应用层级别的数据流,在调整接收窗口前流控制机制不必等待一个信号来达到它的最终目的。中间节点可以使用流控制设置信息来决定它们自己资源相应地分配和修改。通过这种方式,每个中间服务器都可以实现它自定义的资源策略,从而追求更高的连接效率。
在创建合适的资源策略时,流控制的这种灵活性是非常有利的。比如,客户端可以获取图片的首次扫描,将其展示给用户并允许用户预览它的同时获取更多其它关键资源。一旦关键资源获取完毕,再恢复对于图片剩余部分的获取。
除了上述的方法外,HTTP/2提供另一种更多细致级别的控制,这为进一步的优化提供了可能,即Predicting Resource Request
在传统的web应用中,客户端发起一个get请求并通常获取到一个站点的首页index.html。在检查首页内容时,为了完整的渲染页面,客户端会发现首页其实需要更多类似css/javascript等额外资源。客户端在收到初始get请求的响应即index.html后方才知道页面所需要的额外资源,需要进一步发出更多的请求获取这些资源再与页面组合起来渲染页面。这些资源的请求无疑增加了连接的加载时间。
解决方案:由于服务器预先就知道客户端将会需要哪些额外资源,所以服务器可以主动向客户端发送这些额外资源而无需再由客户端自己发起请求来获取额外资源,这就为客户端节省了时间。
HTTP/1.1与HTTP/2由各自不同的方式来实现这个方案。
HTTP/1.1 -- Resource Inlining
HTTP/1.1中,若预先知道页面渲染所需要的额外资源,可以通过一种叫做Resource Inlining的技术在服务器对于初始get请求所响应回来的页面文件中直接获取额外资源。如,若客户端需要某个特定的css来渲染页面,将css文件内联到页面就可以达到目的而无需再发起新请求,这就减少了客户端必须发送的请求数量。
但资源内联存在一些缺陷,内联资源到HTML文件仅适用于小的,基于文本的资源,而对于大的,非文本格式的文件并不可行,因为这会让HTML文件变得臃肿进而降低连接速度,得不偿失。除此之外,由于客户端没有相关机制可以将内联资源重新恢复出来或放入缓存中,所以内联资源是无法从HTML文件分离出来的。若多个页面需要同一个资源,那么每个页面都将内联该资源从而导致每个页面文件体积增大加载速度变慢。
由于上述缺陷,HTTP/2的Server Push就应运而生。
HTTP/2 -- Server Push
由于HTTP/2允许对客户端的初始get请求提供多个并发响应,所以服务器可以将请求的HTML页面以及所需要的额外资源一起发送到客户端,这个过程称为Server Push。这种方式也能完成与Resource Inlining相同的目的同时保持额外推送的资源与HTML文件的分离。这意味客户端可以选择缓存或者将推送的资源与HTML文件分开处理,从而解决了资源内联的缺陷。
简单理解就是额外资源是伴随着目标HTML文件同时被推送给客户端。
该过程从服务器发送一个用于通知客户端其将推送一个资源的PUSH_PROMISE帧开始。该帧仅包含消息头head of message并提前告诉客户端服务器将推送哪个资源。
若该资源已缓存,客户端可通过发送一个RST_STREAM帧作为响应来拒绝该推送。
PUSH_PROMISE可避免客户端向服务器发送重复的请求,因为它知道服务器将推送哪个资源给客户端。
注意 :Server Push 是客户端控制的,若一个客户端需要调整 sever push 的优先级或禁用 Server push,它可以在任意时间通过发送一个
SETTINGS帧来修改这个HTTP/2特性。
尽管该特性有许多潜能,但Server Push也不一定是优化应用的方案。例如,即使客户端已经缓存了资源,某些Web浏览器若不能保证取消推送的请求。如果客户端错误地允许服务器发送重复资源,则Server Push可能会不必要地耗尽连接。所以,Server Push应由开发人员自行决定,保守使用。
5.Compression
使用压缩算法来减少http message在客户端与服务器间传输的大小是优化Web应用常见方法。
HTTP/1.1对于压缩策略有实现问题,即禁止压缩整个message,这是为何?HTTP/2为此又提供了何种解决方案?
HTTP/1.1
类似gzip的程序常被用于压缩http message中所传输的数据,尤其用于压缩css、js文件。
消息头header of message总是以普通文本被发送,尽管消息头很小,但随着请求数量的增多,未经压缩的消息头逐渐成为越来越重的负担;此外,cookies的使用也会使得header变得更大,所以针对消息头的压缩显得越来越重要。
HTTP/2
HTTP/2中一个非常强大的特性就是二进制帧层,通过它可以在更细粒度之上提供更好的控制,这点也体现在对于header的压缩上。
HTTP/2可以从数据中拆分出header,分为header frame和data frame。
HTTP/2指定的压缩程序HPACK可用于压缩header frame,该算法使用哈夫曼编码来编码头部元数据,从而极大地降低了消息头大小。此外,HPACK可以追踪先前传达的元数据字段并根据一个共享于客户端与服务器之间的动态改变的索引进一步压缩它们。
Request #1
method: GET
scheme: https
host: example.com
path: /academy
accept: /image/jpeg
user-agent: Mozilla/5.0 ...
Request #2
method: GET
scheme: https
host: example.com
path: /academy/images
accept: /image/jpeg
user-agent: Mozilla/5.0 ...
两个 request 只有 path 是不同值,当发送Request #2时,客户端可以使用HPACK发送唯一需要的索引值来重建这些相同的字段并加入新字段path的编码。压缩后的header frame如下:
Request #1
method: GET
scheme: https
host: example.com
path: /academy
accept: /image/jpeg
user-agent: Mozilla/5.0 ...
Request #2
path: /academy/images
6.总结
如你所见,经过点对点对比,二者在许多方面都存在不同,尤其是一些优化Web应用性能的更高级别的控制特性以及其他一些改善先前协议缺陷的特性。现在对于二者的区别有了一个大概的认识之后,你可以想想HTTP/2新特性,如:多路复用,流优先级,流控制,服务器推送以及压缩将会对Web开发产生怎样的影响?
更多资料,可以查看以下链接: