• home > theory > CST > network >

    再谈HTTP2性能提升之背后原理—从http1到HTTP2的历史解剖

    Author:[email protected] Date:

    虽然本站早已切换到http2,但是关于http2的理论方面知识,还是需要多加了解。本文是多变优秀http2的文章精炼而成。

    即使千辛万苦,还是把网站升级到http2了,遇坑如《phpcms v9站http升级到https加http2遇到到坑》。

    因为理论相比于 HTTP 1.x ,在同时兼容 HTTP/1.1 完全语义,进一步减少了网络延迟。

    对于前端开发人员来说,无疑减少了在前端方面的优化工作。比如雪碧图&文件合并||内容内嵌||域名分片

    http1.0的缺点

    http1.0被抱怨最多的就是连接无法复用,和head of line blocking这两个问题。

    • 连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大

    • head of line blocking会导致带宽无法被充分利用,以及后续健康请求被阻塞。假设有5个请求同时发出,对于http1.0的实现,在第一个请求没有收到回复之前,后续从应用层发出的请求只能排队,请求2,3,4,5只能等请求1的response回来之后才能逐个发出。一旦请求1的request因为什么原因没有抵达服务器,或者response因为网络阻塞没有及时返回,影响的就是所有后续请求,问题就变得比较严重了。

    理解这两个问题有一个十分重要的前提:客户端是依据域名来向服务器建立连接,一般PC端浏览器会针对单个域名的server同时建立6~8个连接,手机端的连接数则一般控制在4~6个。显然连接数并不是越多越好,资源开销和整体延迟都会随之增大。

    对于http1.0的缺点优化方案

    解决连接无法复用

    HTTP持久连接(persistent connection)可以重用已建立的TCP连接,即在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟(也就是一次TCP的连接不马上释放,允许许多的请求跟响应在一个TCP的连接上发送)——主要目的就是:减少三次握手的RTT延迟与慢启动的适应时间(比如TCP建立连接时三次握手有1.5个RTT(round-trip time)的延迟,为了避免每次请求的都经历握手带来的延迟,应用层会选择不同策略的http长链接方案。又比如TCP在建立连接的初期有慢启动(slow start)的特性,所以连接的重用总是比新建连接性能要好。)。

    • http1.0需要在协议头里设置Connection:Keep-Alive。在header里设置Keep-Alive可以在一定时间内复用连接,具体复用时间的长短可以由服务器控制,一般在15s左右。

    • http1.1之后Connection的默认值就是Keep-Alive,如果要关闭连接复用需要显式的设置Connection:Close

    实际上在 HTTP/1.1 中Connect 这个头部已经没有 Keep-Alive 这个取值了,由于历史原因,很多客户端和服务端,依然保留了这个报文头。

    Keep-Alive解决的核心

    以往,浏览器判断响应数据是否接收完毕,是看连接是否关闭。在使用持久连接后,就不能这样了(以服务器设置的限定时间内关闭),这就要求服务器对持久连接的响应头部一定要返回content-length标识body的长度,供浏览器判断界限。有时,content-length的方法并不是太准确,也可以使用 Transfer-Encoding: chunked 头部发送一串一串的数据,最后由长度为 0 的chunked标识结束。多次 http 请求效果如下图所示:

    16cff86d8df7b4d8 .png

    上图:设置 Connection:Keep-Alive,保持连接在一段时间内不断开。

    HTTP头中一般断点下载时会用到Range和Content-Range实体头

    • Range用户请求头中,指定请求数据的范围(Range计算字节数是从0开始的——0表示第一个字节)。如果用户的请求中含有range ,则服务器的相应代码为206。

      • 第二个500字节:bytes=500-999

      • 表示最后500个字节:bytes=-500

      • 表示500字节以后的范围:bytes=500-

      • 第一个和最后一个字节:bytes=0-0,-1

      • 同时指定几个范围:bytes=500-600,601-999

    • Content-Range用于响应头,用来指明发送给接收方的消息主体的大小。只有当报文长度可以预先判断的时候才起作用。浏览器可以通过 Content-Length 的长度信息,判断出响应实体已结束。那如果 Content-Length 和实体实际长度不一致会怎样?

      • Content-Length 比实际长度短,会造成内容被截断

      • Content-Length 比实体内容长,会造成 pending

    WEB 性能优化时,有一个重要的指标叫 TTFB(Time To First Byte),它代表的是从客户端发出请求到收到响应的第一个字节所花费的时间。大部分浏览器自带的 Network 面板都可以看到这个指标,越短的 TTFB 意味着用户可以越早看到页面内容,体验越好。

    但如果如内容实体来自网络文件、或者是动态生成的。这个时候如果依然想要提前获取到内容实体的长度,只能开一个足够大的 Buffer,等内容全部缓存好了再计算。这并不是一个好的方案,全部缓存到 Buffer 里,第一会消耗更多的内存,第二也会更耗时,让客户端等待过久。

    Transfer-Encoding 在最新的 HTTP/1.1 协议里,就只有 chunked 这个参数,标识当前为分块编码传输。

    HTTP/2 中已经不支持 chunked 这一格式了,因为其本身提供了更加高级的流机制来实现类似功能

    一段时间内的连接复用对PC端浏览器的体验帮助很大,因为大部分的请求在集中在一小段时间以内。比如打开网页,加载一系列的CSS/JS文件。其次就是大文件上传下载。

    但对移动app来说,成效不大,app端的请求比较分散且时间跨度相对较大。所以移动端app一般会从应用层寻求其它解决方案,长连接方案或者伪长连接方案。

    方案一:自定义协议(基于tcp的长链接)

    现在越来越多的移动端app都会建立一条自己的长链接通道,通道的实现是基于tcp协议——于TCP协议之上设计了应用层协议,类似于RPC机制。基于tcp的socket编程技术难度相对复杂很多,而且需要自己制定协议,但带来的回报也很大。信息的上报和推送变得更及时,在请求量爆发的时间点还能减轻服务器压力(http短连接模式会频繁的创建和销毁连接)。不止是IM app有这样的通道,像淘宝这类电商类app都有自己的专属长连接通道了。现在业界也有不少成熟的方案可供选择了,google的protobuf就是其中之一。具体可以参看《干货 | 携程App网络服务通道治理和性能优化

    对于web页面,我们无法应用。因为你没法让别人的浏览器支持你的私有协议。

    方案二:HTTP管线化(http long-polling)

    客户端在初始状态就会发送一个polling请求到服务器,服务器并不会马上返回业务数据,而是等待有新的业务数据产生的时候再返回。所以连接会一直被保持,一旦结束马上又会发起一个新的polling请求,如此反复,所以一直会有一个连接被保持。服务器有新的内容产生的时候,并不需要等待客户端建立一个新的连接。做法虽然简单,但有些难题需要攻克才能实现稳定可靠的业务框架:

    • 和传统的http短链接相比,长连接会在用户增长的时候极大的增加服务器压力

    • 移动端网络环境复杂,像wifi和4g的网络切换,进电梯导致网络临时断掉等,这些场景都需要考虑怎么重建健康的连接通道。

    • 这种polling的方式稳定性并不好,需要做好数据可靠性的保证,比如重发和ack机制。

    • polling的response有可能会被中间代理cache住,要处理好业务数据的过期机制。

    http long-pollingHTTP管线化

    long-polling方式还有一些缺点是无法克服的,比如每次新的请求都会带上重复的header信息,还有数据通道是单向的,主动权掌握在server这边,客户端有新的业务请求的时候无法及时传送。

    对于web页面,现代浏览器都支持边接收数据边渲染(按需加载),一次性把整个响应输出,可能会增加页面白屏时间。

    注:仅HTTP/1.1支持此技术(HTTP/1.0不支持,基于长连接)

    方案三:http streaming

    同long-polling不同的是,server并不会结束初始的streaming请求,而是持续的通过这个通道返回最新的业务数据。显然这个数据通道也是单向的。streaming是通过在server response的头部里增加”Transfer Encoding: chunked”来告诉客户端后续还会有新的数据到来。除了和long-polling相同的难点之外,streaming还有几个缺陷:

    • 有些代理服务器会等待服务器的response结束之后才会将结果推送到请求客户端。对于streaming这种永远不会结束的方式来说,客户端就会一直处于等待response的过程中。

    • 业务数据无法按照请求来做分割,所以客户端没收到一块数据都需要自己做协议解析,也就是说要做自己的协议定制。

    streaming不会产生重复的header数据。

    方案四:web socket

    WebSocket和传统的tcp socket连接相似,也是基于tcp协议,提供双向的数据通道。WebSocket优势在于提供了message的概念,比基于字节流的tcp socket使用更简单,同时又提供了传统的http所缺少的长连接功能。不过WebSocket相对较新,2010年才起草,并不是所有的浏览器都提供了支持。各大浏览器厂商最新的版本都提供了支持。

    解决head of line blocking

    Head of line blocking(以下简称为holb)是http2.0之前网络体验的最大祸源。健康的请求会被不健康的请求影响,而且这种体验的损耗受网络环境影响,出现随机且难以监控。为了解决holb带来的延迟,协议设计者设计了一种新的pipelining机制。

    20160911174859654955.png

    不过pipelining并不是救世主,它也存在不少缺陷:

    • pipelining只能适用于http1.1,一般来说,支持http1.1的server都要求支持pipelining。

    • 只有幂等的请求(GET,HEAD)能使用pipelining,非幂等请求比如POST不能使用,因为请求之间可能会存在先后依赖关系。

    • head of line blocking并没有完全得到解决,server的response还是要求依次返回,遵循FIFO(first in first out)原则。也就是说如果请求1的response没有回来,2,3,4,5的response也不会被送回来。

    • 绝大部分的http代理服务器不支持pipelining。

    • 和不支持pipelining的老服务器协商有问题。

    • 可能会导致新的Front of queue blocking问题。

    正是因为有这么多的问题,各大浏览器厂商要么是根本就不支持pipelining,要么就是默认关掉了pipelining机制,而且启用的条件十分苛刻。可以参考chrome对于pipeling的问题描述

    HTTP2的优势

    为什么么能在不改动 HTTP/1.x 的语义、方法、状态码、URI 以及首部字段….. 的情况下, HTTP/2 是如何做到「突破 HTTP1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量」

    关键之一就是在 应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层。


    在二进制分帧层中, HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码 ,其中 HTTP1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 DATA frame 里面
    HTTP/2 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。

    二进制分帧

    http1.x诞生的时候是明文协议,其格式由三部分组成:start line(request line或者status line),header,body。要识别这3部分就要做协议解析,http1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑http2.0的协议解析决定采用二进制格式,实现方便且健壮

    http2.0用binary格式定义了一个一个的frame,和http1.x的格式对比如下图:


    20160911174955780620.png

    http2.0的格式定义更接近tcp层的方式,这种二机制的方式十分高效且精简。

    帧(frame)和流(stream)

    帧和流是http2的2个核心概念。是发送机制、多路复用、优先级控制等的基石。

    帧(frame)

    帧是HTTP/2中数据传输的最小单位

    帧不仅要细分表达HTTP/1.x中的各个部份,也优化了HTTP/1.x表达得不好的地方,同时还增加了HTTP/1.x表达不了的方式。

    所有帧都以固定的 9 字节大小的头作为帧开始,后跟可变长度的有效载荷 payload。

    +------------------------------------------+

    |               Length (24)                   |

    +---------------+---------------+--------+

    |   Type (8)    |   Flags (8)   |

    +---------------+---------------+--------+

    |R|       Stream Identifier (31)          |

    +=+===================+

        |      Frame Payload (0...)            ...

    +-------------------------------------------+

    帧头的字段定义如下:
    • length定义了整个frame的开始到结束

      帧有效负载的长度表示为无符号的 24 位整数。除非接收方为 SETTINGS_MAX_FRAME_SIZE 设置了较大的值(详情见这里),否则不得发送大于2 ^ 14(16,384)的值。帧头的 9 个八位字节不包含在此长度值中。

    • type定义frame的类型(一共10种)

      8 位用来表示帧类型的。帧类型确定帧的格式和语义。实现方必须忽略并丢弃任何类型未知的帧——在HTTP/2的标准中定义了10种不同的类型,包括上面所说的HEADERS frame和 DATA frame(HEADERS 帧在 DATA 帧前面)。此外还有:

      • PRIORITY(设置流的优先级)

      • RST_STREAM(终止流)

      • SETTINGS(设置此连接的参数)

      • PUSH_PROMISE(服务器推送)

      • PING(测量RTT)

      • GOAWAY(终止连接)

      • WINDOW_UPDATE(流量控制)

      • CONTINUATION(继续传输头部数据)

    • flags定义一些重要的参数,

      这个字段是为特定于帧类型的布尔标志保留的 8 位字段,为标志分配特定于指示帧类型的语义。没有为特定帧类型定义语义的标志必须被忽略,并且必须在发送时保持未设置 (0x0)。常用的标志位有:

      • END_HEADERS 表示头数据结束,相当于 HTTP/1 里头后的空行(“\r\n”),

      • END_STREAM 表示单方向数据发送结束(即 EOS,End of Stream),相当于 HTTP/1 里 Chunked 分块结束标志(“0\r\n\r\n”)。

    • stream id用作流控制

      流标识符 (参见 第 5.1.1 节),表示为无符号 31 位整数。值 0x0 保留用于与整个连接相关联的帧,而不是单个流。stream ID 的作用:

      • 实现多路复用的关键。接收端的实现可以根据这个 ID 并发组装消息。同一个 stream 内 frame 必须是有序的SETTINGS_MAX_CONCURRENT_STREAMS 控制着最大并发数

      • 推送依赖性请求的关键。客户端发起的流是奇数编号,服务端发起的流是偶数编号

    • payload就是request的正文了

    流(stream)

    “流”在HTTP/2中是一个逻辑上的概念,就是说在一个TCP连接上,我们可以向对方不断发送一个个的消息,这里每一个消息看成是一帧,而每一帧有个stream identifier的字段标明这一帧属于哪个“流”(流的ID都是奇数,说明是由客户端发起的,偶数则为服务端发起),然后在对方接收时,根据stream identifier拼接每个“流”的所有帧组成一整块数据。我们把HTTP/1.x每个请求都当作一个“流”,那么请求化成多个流,请求响应数据切成多个帧,不同流中的帧交错地发送给对方,这就是HTTP/2中的多路复用

      


    多路复用 (Multiplexing)"|连接共享

    在过去, HTTP 性能优化的关键并不在于高带宽,而是低延迟。TCP 连接会随着时间进行自我「调谐」,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐则被称为 TCP 慢启动。具体复习:《再深谈TCP/IP三步握手&四步挥手原理及衍生问题—长文解剖IP》、《从网卡发送数据再谈TCP/IP协议—网络传输速度计算-网卡构造

    由于这种原因,让原本就具有突发性和短时性的 HTTP 连接变的十分低效

    HTTP/2 通过让所有数据流共用同一个连接,可以更有效地使用 TCP 连接,让高带宽也能真正的服务于 HTTP 的性能提升——只需要创建一个新流即可,这不需要多少时间。单连接有如下优势:

    1. 单连接多资源的方式,减少服务端的链接压力,内存占用更少,连接吞吐量更大

    2. 由于 TCP 连接的减少而使网络拥塞状况得以改善,同时慢启动时间的减少,使拥塞和丢包恢复速度更快

    多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。

    众所周知 ,在 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制(这也是为何一些站点会有多个静态资源 CDN 域名的原因之一)。超过限制数目的请求会被阻塞」。

    Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.

    source:RFC-2616-8.1.4 Practical Considerations

    下图总结了不同浏览器对该限制的数目(来源:https://www.stevesouders.com/blog/2008/03/20/roundup-on-parallel-connections/

    多路复用代替原来的序列和阻塞机制,所有就是请求的都是通过一个 TCP 连接并发完成。同时也很好的解决了浏览器限制同一个域名下的请求数量的问题。

    基于二进制分帧层,HTTP 2.0可以在共享TCP连接的基础上,同时发送请求和响应HTTP消息被分解为独立的帧,而不破坏消息本身的语义,交错发送出去,最后在另一端根据流ID和首部将它们重新组合起来。 

    我们来对比下HTTP 1.x和HTTP 2.0,假设不考虑HTTP1.x的pipeline机制,双方四层都是一个TCP连接。客户端向服务度发起三个图片请求/image1.jpg,/image2.jpg,/image3.jpg。 

    • HTTP 1.x发起请求是串行的,image1返回后才能再发起image2,image2返回后才能再发起image3。 

    • HTTP 2.0建立一条TCP连接后,并行传输着3个数据流,客户端向服务端乱序发送stream1~3的一系列的DATA帧,与此同时,服务端已经在返回stream 1的DATA帧 

    HTTP 1.x发起请求是串行的 http2 多路复用案例


    HTTP 2.0成功解决了HTTP 1.x的队首阻塞问题TCP层的阻塞仍无法解决——HTTP 2.0会带来新的性能瓶颈。因为现在所有的压力集中在底层一个TCP连接之上,所以有了后面的http,顺手安利《《浅谈QUIC/http3协议原理与性能分析及部署方案》》),同时,也不需要通过pipeline机制多条TCP连接来实现并行请求与响应。减少了TCP连接数对服务器性能也有很大的提升

    stream id的核心地位

    上面协议解析中提到的stream id就是用作连接共享机制的:

    一个request对应一个stream并分配一个id,这样一个连接上可以有多个stream每个stream的frame可以随机的混杂在一起,接收方可以根据stream id将frame再归属到各自不同的request里面。因而 HTTP/2 能多路复用(Multiplexing) ,允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息




    因此 HTTP/2 可以很容易的去实现多流并行而不用依赖建立多个 TCP 连接HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息并行地在同一个 TCP 连接上双向交换消息

    HTTP/2 不再依赖 TCP 链接去实现多流并行了,在 HTTP/2 中:

    • 同域名下所有通信都在单个连接上完成,同个域名只需要占用一个 TCP 连接,使用一个连接并行发送多个请求和响应

    • 单个连接可以承载任意数量的双向数据流,单个连接上可以并行交错的请求和响应,之间互不干扰。

    • 数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装。每个请求都可以带一个 31bit 的优先值,0 表示最高优先级, 数值越大优先级越低。

    前面还提到过连接共享之后,需要优先级和请求依赖的机制配合才能解决关键请求被阻塞的问题。http2.0里的每个stream都可以设置又优先级(Priority)和依赖(Dependency)优先级高的stream会被server优先处理和返回给客户端,stream还可以依赖其它的sub streams优先级和依赖都是可以动态调整的

    动态调整在有些场景下很有用,假想用户在用你的app浏览商品的时候,快速的滑动到了商品列表的底部,但前面的请求先发出,如果不把后面的请求优先级设高,用户当前浏览的图片要到最后才能下载完成,显然体验没有设置优先级好。同理依赖在有些场景下也有妙用。

    优化Web性能有一个常用的技术,就是图片延迟加载,目的是除了节省流量外,还能避免图片资源与其他重要的脚本资源竞争下载。

    比如给CSS与JS设置比图片更高的优先级,prefetch就可以给一个更低的优先级

    HTTP/2提供了流的优先级与依赖性这种机制,可用 HEADERS 帧或 PRIORITY 帧设置,不过协议并没有提供如何处理优先级的具体算法,这可由服务器或者客服端灵活应对。


    首部压缩(Header Compression)

    http1.x的header由于cookie和user agent很容易膨胀,而且每次都要重复发送。

    当这种请求数很多的时候,会导致网络的吞吐率不高。并且,比较大的HTTP头部会迅速占满慢启动过程中的拥塞窗口,导致延迟加大。所以HTTP头的压缩显得很有必要

    HTTP/1.1并不支持 HTTP 首部压缩(gzip只会压缩body),为此 SPDY 和 HTTP/2 应运而生

    这里普及一个小知识点。现在大家都知道tcp有slow start的特性,三次握手之后开始发送tcp segment,第一次能发送的没有被ack的segment数量是由initial tcp window大小决定的。这个initial tcp window根据平台的实现会有差异,但一般是2个segment或者是4k的大小(一个segment大概是1500个字节),也就是说当你发送的包大小超过这个值的时候,要等前面的包被ack之后才能发送后续的包,显然这种情况下延迟更高。intial window也并不是越大越好,太大会导致网络节点的阻塞,丢包率就会增加。http的header现在膨胀到有可能会超过这个intial window的值了,所以更显得压缩header的重要性。

    压缩算法的选择

    SPDY/2使用的是gzip压缩算法,但后来出现的两种攻击方式BREACH和CRIME使得即使走ssl的SPDY也可以被破解内容,最后综合考虑采用的是一种叫HPACK的压缩算法。这两个漏洞和相关算法可以点击链接查看更多的细节,不过这种漏洞主要存在于浏览器端,因为需要通过javascript来注入内容并观察payload的变化。

    现在SPDY 使用的是通用的DEFLATE 算法(同时使用了 LZ77 算法与哈夫曼编码(Huffman Coding)的一个无损数据压缩算法,如PNG压缩),而 HTTP/2 则使用了专门为首部压缩而设计的 HPACK 算法,具体推荐阅读《详解 HTTP/2 头压缩算法 —— HPACK》。

    http2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。高效的压缩算法可以很大的压缩header,减少发送包的数量从而降低延迟。

    header fields表

    “首部表”,表中用索引代表首部名,或者首部键-值对,上一次发送两端都会记住已发送过哪些首部,下一次发送只需要传输差异的数据,相同的数据直接用索引表示即可,另外还可以选择地对首部值压缩后再传输。按照这样的设计,两次轮询请求的首部基本是一样的,那之后的请求基本只需要发送几个索引就可以了。

    http2服务端与客服端共同维护的header fields表

    “首部表”有两种,一种是静态表,即HTTP/2协议内置了常用的一些首部名和首部键值对。另一种是动态表,保存自定义的首部或五花八门的键值对等,动态表可以通过SETTINGS帧的SETTINGS_HEADER_TABLE_SIZE规定大小。

    服务端推送(Server Push)

    服务端推送是一种在客户端请求之前发送数据的机制。在 HTTP/2 中,服务器可以对客户端的一个请求发送多个响应。Server Push 让 HTTP1.x 时代使用内嵌资源的优化手段变得没有意义;如果一个请求是由你的主页发起的,服务器很可能会响应主页内容、logo 以及样式表,因为它知道客户端会用到这些东西。这相当于在一个 HTML 文档内集合了所有的资源,不过与之相比,服务器推送还有一个很大的优势:可以缓存!也让在遵循同源的情况下,不同页面之间可以共享缓存资源成为可能

    http2.0引入RST_STREAM类型的frame,可以在不断开连接的前提下取消某个request的stream,表现更好:

    服务器在接受到请求时,分析出要推送的资源,先发个 PUSH_PROMISE 帧给浏览器。此帧包含一个新的流ID,还有header block fragment字段,内容是请求的头部信息,可理解为服务器模拟浏览器发起请求,然后再发送各个response header和response body。浏览器收到 PUSH_PROMISE 帧时,根据header block fragment字段里的url,可以知道当前有没有缓存,从而判断是否要接收。如果不要,浏览器就要发送个 RST_STREAM 来终止服务器推送

    如果浏览器不要这个推送,就会出现浪费流量的现象,因为整个过程都是异步的,在服务器接收到RST_STREAM时,响应很有可能部份发出或者全部发出了。这种情况只能视场景而定,若是流量浪费不能容忍,我们可以使用prefetch来替代,让浏览器尽早发现需要的资源,而HTTP/2中创建新的请求并不需要多少时间,所以大概多了个RTT的时间。

    服务器推送,此推送非彼推送,一开始以为,是不是以后可以抛弃轮询这种技术了?并不是,该轮询还是要轮询。因为HTTP2建立的并不是web sock 类似的双向通信协议。

    HTTP/2 虽然也支持 Server Push,但是服务器只能主动将资源推送到客户端缓存!并不允许将数据推送到客户端里跑的 Web App 本身。服务器推送只能由浏览器处理,不会在应用程序代码中弹出服务器数据,这意味着应用程序没有 API 来获取这些事件的通知。

    为了接近实时地将数据推送给 Web App, HTTP/2 可以结合 SSE(Server-Sent Event)使用。

    对于web开发来说,服务端推送对 Web App 是隐藏的,完全由浏览器处理。

    重置连接表现更好

    很多app客户端都有取消图片下载的功能场景,对于http1.x来说,是通过设置tcp segment里的reset flag来通知对端关闭连接的。这种方式会直接断开连接,下次再发请求就必须重新建立连接。http2.0引入RST_STREAM类型的frame,可以在不断开连接的前提下取消某个request的stream,表现更好。

    流量控制(Flow Control)

    TCP协议通过sliding window的算法来做流量控制。发送方有个sending window,接收方有receive window。http2.0的flow control是类似receive window的做法,数据的接收方通过告知对方自己的flow window大小表明自己还能接收多少数据。只有Data类型的frame才有flow control的功能。对于flow control,如果接收方在flow window为零的情况下依然更多的frame,则会返回block类型的frame,这张场景一般表明http2.0的部署出了问题。

    更安全的SSL

    HTTP2.0使用了tls的拓展ALPN来做协议升级,除此之外加密这块还有一个改动,HTTP2.0对tls的安全性做了近一步加强,通过黑名单机制禁用了几百种不再安全的加密算法,一些加密算法可能还在被继续使用。如果在ssl协商过程当中,客户端和server的cipher suite没有交集,直接就会导致协商失败,从而请求失败。在server端部署http2.0的时候要特别注意这一点。

    HTTP/2通信过程

    db345660b42beee83e7b27b700a728821444744168.jpeg

    HTTP2的发展概略

    一、http

    HTTP协议经过多年的使用,发现了一些不足,主要是性能方面的,包括:

    • HTTP的连接问题,HTTP客户端和服务器之间的交互是采用请求/应答模式,在客户端请求时,会建立一个HTTP连接,然后发送请求消息,服务端给出应答消息,然后连接就关闭了。(后来的HTTP1.1支持持久连接)

    • 因为TCP连接的建立过程是有开销的,如果使用了SSL/TLS开销就更大。

    • 在浏览器里,一个网页包含许多资源,包括HTML,CSS,JavaScript,图片等等,这样在加载一个网页时要同时打开连接到同一服务器的多个连接。

    • HTTP消息头问题,现在的客户端会发送大量的HTTP消息头,由于一个网页可能需要50-100个请求,就会有相当大的消息头的数据量。

    • HTTP通信方式问题,HTTP的请求/应答方式的会话都是客户端发起的,缺乏服务器通知客户端的机制,在需要通知的场景,如聊天室,游戏,客户端应用需要不断地轮询服务器。


    而SPDY和WebSocket是从不同的角度来解决这些不足中的一部分。除了这两个技术,还有其他技术也在针对这些不足提出改进。

    二、SPDY

    SPDY的主要目的是减少50%以上的页面加载时间,但是呢不增加部署的复杂性,不影响客户端和服务端的Web应用,只需要浏览器和Web服务器支持SPDY。主要有以下几:

    • 多路复用,一个TCP连接上同时跑多个HTTP请求。请求可设定优先级。

    • 去除不需要的HTTP头,压缩HTTP头,以减少需要的网络带宽。

    • 使用了SSL作为传输协议提供数据安全。

    • 对传输的数据使用gzip进行压缩

    • 提供服务方发起通信,并向客户端推送数据的机制。

    实质上,SPDY就是想不影响HTTP语义的情况下,替换HTTP底层传输的协议来加快页面加载时间。

    SPDY的解决办法就是设计了一个会话层协议--帧协议,解决多路复用,优先级等问题,然后在其上实现了HTTP的语义。

    SPDY的诞生和表现说明了两件事情:一是在现有互联网设施基础和http协议广泛使用的前提下,是可以通过修改协议层来优化http1.x的。二是针对http1.x的修改确实效果明显而且业界反馈很好。正是这两点让IETF(Internet Enginerring Task Force)开始正式考虑制定HTTP2.0的计划,最后决定以SPDY/3为蓝图起草HTTP2.0,SPDY的部分设计人员也被邀请参与了HTTP2.0的设计。

    三、WebSocket

    WebSocket则提供使用一个TCP连接进行双向通讯的机制,包括网络协议和API,以取代网页和服务器采用HTTP轮询进行双向通讯的机制。


    本质上来说,WebSocket是不限于HTTP协议的,但是由于现存大量的HTTP基础设施,代理,过滤,身份认证等等,WebSocket借用HTTP和HTTPS的端口。

    由于使用HTTP的端口,因此TCP连接建立后的握手消息是基于HTTP的,由服务器判断这是一个HTTP协议,还是WebSocket协议。 WebSocket连接除了建立和关闭时的握手,数据传输和HTTP没丁点关系了。
    WebSocket也有自己一套帧协议。

    四、SPDY和WebSocket的关系

    SPDY和WebSocket的关系比较复杂。

    1. 补充关系,二者侧重点不同。SPDY更侧重于给Web页面的加载提速,而WebSocket更强调为Web应用提供一种双向的通讯机制以及API。

    2. 竞争关系,二者解决的问题有交集,比如在服务器推送上SPDY和WebSocket都提供了方案。

    3. 承载关系,试想,如果SPDY的标准化早于WebSocket,WebSocket完全可以侧重于API,利用SPDY的帧机制和多路复用机制实现该API。 Google提出草案,说WebSocket可以跑在SPDY之上。WebSocket的连接建立在SPDY的流之上,将WebSocket的帧映射到SPDY的帧上。

    4. 融合关系,如微软在HTTP Speed+Mobility中所做的。


    http2的竞争兄弟

    1. HTTP Speed+Mobility

    还有一个有趣的技术叫做HTTP Speed+Mobility,和SPDY一样都是HTTP 2.0标准的竞争者,HTTP Speed+Mobility来自微软。HTTP SM借鉴了SPDY和WebSocket的协议,将二者揉为一体,又有所取舍。


    HTTP SM的设计原则包括:
    • 保留HTTP的语义,这一点和SPDY一致,但也正应如此,抛弃了SPDY里的ServerPush。

    • 遵守分层的网络架构,TCP能做的,HTTP SM不做,因此去除了SPDY的流控。

    • 使用现有标准,因此使用HTTP/1.1 Upgrade header机制,借用了WebSocket的握手机制和帧格式(RFC6455)。

    • 客户端掌握内容的控制,因此不强制使用压缩和SSL/TLS。

    • 考虑到网络的费用和电力,这点考虑到了移动设备以及物联网,提供了Credit Control机制。


    HTTP SM分以下几层:
    • 会话层和帧协议,这部分取自WebSocket协议。包括握手机制,以及帧格式。

    • 流层(包括多路复用),这部分主要借鉴SPDY,包括多路复用,流优先级,但增加了Credit Control。这部分作为 WebSocket协议的扩展。

    • HTTP层,在流层上实现HTTP语义,这部分也借鉴自SPDY。

    2.  Network-Friendly HTTP

    NF是HTTP 2.0候选方案之一,主要提出以下改进:

    • 对HTTP头的名称进行二进制编码

    • 对通用HTTP头进行分组

    • 请求/应答的多路复用

    • 分层模型

    NF同样定义了帧和流,


    3. WAKA

    WAKA也是HTTP 2.0候选方案之一,是HTTP协议原作者Roy Fielding提出的一个提案。

    WAKA支持多路复用,支持优先级。WAKA提出了两个新的HTTP方法,RENDER和MONITOR。


    按照OSI网络分层模型,IP是网络层协议,TCP是传输层协议,而HTTP是应用层的协议。在这三者之间,SPDY和WebSocket都是与HTTP相关的协议,而TCP是HTTP底层的协议。



    强烈推荐阅读  


    参考资料:




    转载本站文章《再谈HTTP2性能提升之背后原理—从http1到HTTP2的历史解剖》,
    请注明出处:https://www.zhoulujun.cn/html/theory/ComputerScienceTechnology/network/2016_0714_7862.html