在咱们所处的互联网世界中,HTTP协议算得上是使用最普遍的网络协议。最近http2.0的诞生使得它再次互联网技术圈关注的焦点。任何事物的消退和新生都有其背后推进的力量。对于HTTP来讲,这力量复杂来讲是各类技术细节的演进,简单来讲是用户体验和感知的进化。用户老是但愿网络上的信息能尽量快的抵达眼球,越快越好,正是这种对“快”对追逐催生了今天的http2.0。javascript
http2.0的前世是http1.0和http1.1这两兄弟。虽然以前仅仅只有两个版本,但这两个版本所包含的协议规范之庞大,足以让任何一个有经验的工程师为之头疼。http1.0诞生于1996年,协议文档足足60页。以后第三年,http1.1也随之出生,协议文档膨胀到了176页。不过和咱们手机端app升级不一样的是,网络协议新版本并不会立刻取代旧版本。实际上,1.0和1.1在以后很长的一段时间内一直并存,这是因为网络基础设施更新缓慢所决定的。今天的http2.0也是同样,新版协议再好也须要业界的产品锤炼,须要基础设施逐年累月的升级换代才能普及。css
理解http协议以前必定要对TCP有必定基础的了解。HTTP是创建在TCP协议之上,TCP协议做为传输层协议其实离应用层并不远。HTTP协议的瓶颈及其优化技巧都是基于TCP协议自己的特性。好比TCP创建链接时三次握手有1.5个RTT(round-trip time)的延迟,为了不每次请求的都经历握手带来的延迟,应用层会选择不一样策略的http长连接方案。又好比TCP在创建链接的初期有慢启动(slow start)的特性,因此链接的重用老是比新建链接性能要好。html
http诞生之初主要是应用于web端内容获取,那时候内容还不像如今这样丰富,排版也没那么精美,用户交互的场景几乎没有。对于这种简单的获取网页内容的场景,http表现得还算不错。但随着互联网的发展和web2.0的诞生,更多的内容开始被展现(更多的图片文件),排版变得更精美(更多的css),更复杂的交互也被引入(更多的js)。用户打开一个网站首页所加载的数据总量和请求的个数也在不断增长。今天绝大部分的门户网站首页大小都会超过2M,请求数量能够多达100个。另外一个普遍的应用是在移动互联网的客户端app,不一样性质的app对http的使用差别很大。对于电商类app,加载首页的请求也可能多达10多个。对于微信这类IM,http请求可能仅限于语音和图片文件的下载,请求出现的频率并不算高。前端
影响一个网络请求的因素主要有两个,带宽和延迟。今天的网络基础建设已经使得带宽获得极大的提高,大部分时候都是延迟在影响响应速度。http1.0被抱怨最多的就是链接没法复用,和head of line blocking这两个问题。理解这两个问题有一个十分重要的前提:客户端是依据域名来向服务器创建链接,通常PC端浏览器会针对单个域名的server同时创建6~8个链接,手机端的链接数则通常控制在4~6个。显然链接数并非越多越好,资源开销和总体延迟都会随之增大。java
链接没法复用会致使每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。android
head of line blocking会致使带宽没法被充分利用,以及后续健康请求被阻塞。假设有5个请求同时发出,以下图:ios
[图1]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/http1.0_blocking.png?raw=true" width="485">nginx
对于http1.0的实现,在第一个请求没有收到回复以前,后续从应用层发出的请求只能排队,请求2,3,4,5只能等请求1的response回来以后才能逐个发出。网络通畅的时候性能影响不大,一旦请求1的request由于什么缘由没有抵达服务器,或者response由于网络阻塞没有及时返回,影响的就是全部后续请求,问题就变得比较严重了。git
http1.0协议头里能够设置Connection:Keep-Alive。在header里设置Keep-Alive能够在必定时间内复用链接,具体复用时间的长短能够由服务器控制,通常在15s左右。到http1.1以后Connection的默认值就是Keep-Alive,若是要关闭链接复用须要显式的设置Connection:Close。一段时间内的链接复用对PC端浏览器的体验帮助很大,由于大部分的请求在集中在一小段时间之内。但对移动app来讲,成效不大,app端的请求比较分散且时间跨度相对较大。因此移动端app通常会从应用层寻求其它解决方案,长链接方案或者伪长链接方案:github
如今愈来愈多的移动端app都会创建一条本身的长连接通道,通道的实现是基于tcp协议。基于tcp的socket编程技术难度相对复杂不少,并且须要本身制定协议,但带来的回报也很大。信息的上报和推送变得更及时,在请求量爆发的时间点还能减轻服务器压力(http短链接模式会频繁的建立和销毁链接)。不止是IM app有这样的通道,像淘宝这类电商类app都有本身的专属长链接通道了。如今业界也有很多成熟的方案可供选择了,google的protobuf就是其中之一。
long-polling能够用下图表示:
[图2]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/http_polling.png?raw=true" width="356">
客户端在初始状态就会发送一个polling请求到服务器,服务器并不会立刻返回业务数据,而是等待有新的业务数据产生的时候再返回。因此链接会一直被保持,一旦结束立刻又会发起一个新的polling请求,如此反复,因此一直会有一个链接被保持。服务器有新的内容产生的时候,并不须要等待客户端创建一个新的链接。作法虽然简单,但有些难题须要攻克才能实现稳定可靠的业务框架:
和传统的http短连接相比,长链接会在用户增加的时候极大的增长服务器压力
移动端网络环境复杂,像wifi和4g的网络切换,进电梯致使网络临时断掉等,这些场景都须要考虑怎么重建健康的链接通道。
这种polling的方式稳定性并很差,须要作好数据可靠性的保证,好比重发和ack机制。
polling的response有可能会被中间代理cache住,要处理好业务数据的过时机制。
long-polling方式还有一些缺点是没法克服的,好比每次新的请求都会带上重复的header信息,还有数据通道是单向的,主动权掌握在server这边,客户端有新的业务请求的时候没法及时传送。
http streaming流程大体以下:
[图3]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/http_streaming.png?raw=true" width="363">
同long-polling不一样的是,server并不会结束初始的streaming请求,而是持续的经过这个通道返回最新的业务数据。显然这个数据通道也是单向的。streaming是经过在server response的头部里增长"Transfer Encoding: chunked"来告诉客户端后续还会有新的数据到来。除了和long-polling相同的难点以外,streaming还有几个缺陷:
有些代理服务器会等待服务器的response结束以后才会将结果推送到请求客户端。对于streaming这种永远不会结束的方式来讲,客户端就会一直处于等待response的过程当中。
业务数据没法按照请求来作分割,因此客户端没收到一块数据都须要本身作协议解析,也就是说要作本身的协议定制。
streaming不会产生重复的header数据。
WebSocket和传统的tcp socket链接类似,也是基于tcp协议,提供双向的数据通道。WebSocket优点在于提供了message的概念,比基于字节流的tcp socket使用更简单,同时又提供了传统的http所缺乏的长链接功能。不过WebSocket相对较新,2010年才起草,并非全部的浏览器都提供了支持。各大浏览器厂商最新的版本都提供了支持。
Head of line blocking(如下简称为holb)是http2.0以前网络体验的最大祸源。正如图1中所示,健康的请求会被不健康的请求影响,并且这种体验的损耗受网络环境影响,出现随机且难以监控。为了解决holb带来的延迟,协议设计者设计了一种新的pipelining机制。
pipelining的流程图能够用下图表示:
[图4]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/pipelining.png?raw=true" width="375">
和图一相比最大的差异是,请求2,3,4,5不用等请求1的response返回以后才发出,而是几乎在同一时间把request发向了服务器。2,3,4,5及全部后续共用该链接的请求节约了等待的时间,极大的下降了总体延迟。下图能够清晰的看出这种新机制对延迟的改变:
[图5]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/http_pipelining_performance.png?raw=true" width="625">
不过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的问题描述。
为了解决延迟带来的苦恼,永远都会有聪明的探索者找出新的捷径来。互联网的蓬勃兴盛催生出了各类新奇技巧,咱们来依次看下这些“捷径”及各自的优缺点。
Spriting指的是将多个小图片合并到一张大的图片里,这样多个小的请求就被合并成了一个大的图片请求,而后再利用js或者css文件来取出其中的小张图片使用。好处显而易见,请求数减小,延迟天然低。坏处是文件的粒度变大了,有时候咱们可能只须要其中一张小图,却不得不下载整张大图,cache处理也变得麻烦,在只有一张小图过时的状况下,为了得到最新的版本,不得不从服务器下载完整的大图,即便其它的小图都没有过时,显然浪费了流量。
Inlining的思考角度和spriting相似,是将额外的数据请求经过base64编码以后内嵌到一个总的文件当中。好比一个网页有一张背景图,咱们能够经过以下代码嵌入:
background: url(data:image/png;base64,<data>)
data部分是base64编码以后的字节码,这样也避免了一次多余的http请求。但这种作法也有着和spriting相同的问题,资源文件被绑定到了其它文件,粒度变得难以控制。
Concatenation主要是针对js这类文件,如今前端开发交互愈来愈多,零散的js文件也在变多。将多个js文件合并到一个大的文件里在作一些压缩处理也能够减少延迟和传输的数据量。但一样也面临着粒度变大的问题,一个小的js代码改动会致使整个js文件被下载。
前面我提到过很重要的一点,浏览器或者客户端是根据domain(域名)来创建链接的。好比针对www.example.com只容许同时创建2个链接,但mobile.example.com被认为是另外一个域名,能够再创建两个新的链接。依次类推,若是我再多创建几个sub domain(子域名),那么同时能够创建的http请求就会更多,这就是Domain Sharding了。链接数变多以后,受限制的请求就不须要等待前面的请求完成才能发出了。这个技巧被大量的使用,一个颇具规模的网页请求数能够超过100,使用domain sharding以后同时创建的链接数能够多到50个甚至更多。
这么作固然增长了系统资源的消耗,但如今硬件资源升级很是之快,和用户宝贵的等待时机相比起来实在微不足道。
domain sharding还有一大好处,对于资源文件来讲通常是不须要cookie的,将这些不一样的静态资源文件分散在不一样的域名服务器上,能够减少请求的size。
不过domain sharding只有在请求数很是之多的场景下才有明显的效果。并且请求数也不是越多越好,资源消耗是一方面,另外一点是因为tcp的slow start会致使每一个请求在初期都会经历slow start,还有tcp 三次握手,DNS查询的延迟。这一部分带来的时间损耗和请求排队一样重要,到底怎么去平衡这两者就须要取一个可靠的链接数中间值,这个值的最终肯定要经过反复的测试。移动端浏览器场景建议不要使用domain sharding,具体细节参考这篇文章。
http1.0和1.1虽然存在这么多问题,业界也想出了各类优化的手段,但这些方法手段都是在尝试绕开协议自己的缺陷,都有种隔靴搔痒,治标不治本的感受。直到2012年google如一声惊雷提出了SPDY的方案,你们才开始从正面看待和解决老版本http协议自己的问题,这也直接加速了http2.0的诞生。实际上,http2.0是以SPDY为原型进行讨论和标准化的。为了给http2.0让路,google已决定在2016年再也不继续支持SPDY开发,但在http2.0出生以前,SPDY已经有了至关规模的应用,做为一个过渡方案恐怕在还将一段时间内继续存在。如今很多app客户端和server都已经使用了SPDY来提高体验,http2.0在老的设备和系统上还没法使用(iOS系统只有在iOS9+上才支持),因此能够预见将来几年spdy将和http2.0共同服务的状况。
SPDY的目标在一开始就是瞄准http1.x的痛点,即延迟和安全性。咱们上面通篇都在讨论延迟,至于安全性,因为http是明文协议,其安全性也一直被业界诟病,不过这是另外一个大的话题。若是以下降延迟为目标,应用层的http和传输层的tcp都是都有调整的空间,不过tcp做为更底层协议存在已达数十年之久,其实现已深植全球的网络基础设施当中,若是要动必然伤经动骨,业界响应度必然不高,因此SPDY的手术刀对准的是http。
下降延迟,客户端的单链接单请求,server的FIFO响应队列都是延迟的大头。
http最初设计都是客户端发起请求,而后server响应,server没法主动push内容到客户端。
压缩http header,http1.x的header愈来愈膨胀,cookie和user agent很容易让header的size增至1kb大小,甚至更多。并且因为http的无状态特性,header必须每次request都重复携带,很浪费流量。
为了增长业界响应的可能性,聪明的google一开始就避开了从传输层动手,并且打算利用开源社区的力量以提升扩散的力度,对于协议使用者来讲,也只须要在请求的header里设置user agent,而后在server端作好支持便可,极大的下降了部署的难度。SPDY的设计以下:
[图6]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/spdy.png?raw=true" width="215">
SPDY位于HTTP之下,TCP和SSL之上,这样能够轻松兼容老版本的HTTP协议(将http1.x的内容封装成一种新的frame格式),同时可使用已有的SSL功能。SPDY的功能能够分为基础功能和高级功能两部分,基础功能默认启用,高级功能须要手动启用。
多路复用(multiplexing)。多路复用经过多个请求stream共享一个tcp链接的方式,解决了http1.x holb(head of line blocking)的问题,下降了延迟同时提升了带宽的利用率。
请求优先级(request prioritization)。多路复用带来一个新的问题是,在链接共享的基础之上有可能会致使关键请求被阻塞。SPDY容许给每一个request设置优先级,这样重要的请求就会优先获得响应。好比浏览器加载首页,首页的html内容应该优先展现,以后才是各类静态资源文件,脚本文件等加载,这样能够保证用户能第一时间看到网页内容。
header压缩。前面提到过几回http1.x的header不少时候都是重复多余的。选择合适的压缩算法能够减少包的大小和数量。SPDY对header的压缩率能够达到80%以上,低带宽环境下效果很大。
server推送(server push)。http1.x只能由客户端发起请求,而后服务器被动的发送response。开启server push以后,server经过X-Associated-Content header(X-开头的header都属于非标准的,自定义header)告知客户端会有新的内容推送过来。在用户第一次打开网站首页的时候,server将资源主动推送过来能够极大的提高用户体验。
server暗示(server hint)。和server push不一样的是,server hint并不会主动推送内容,只是告诉有新的内容产生,内容的下载仍是须要客户端主动发起请求。server hint经过X-Subresources header来通知,通常应用场景是客户端须要先查询server状态,而后再下载资源,能够节约一次查询请求。
SPDY的成绩能够用google官方的一个数字来讲明:页面加载时间相比于http1.x减小了64%。并且各大浏览器厂商在SPDY诞生以后的1年多里都陆续支持了SPDY,很多大厂app和server端框架也都将SPDY应用到了线上的产品当中。
google的官网也给出了他们本身作的一份测试数据。测试对象是25个访问量排名靠前的网站首页,家用网络%1的丢包率,每一个网站测试10次取平均值。结果以下:
[图7]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/spdy_result.png?raw=true" width="860">
不开启ssl的时候提高在 27% - 60%,开启以后为39% - 55%。
这份测试结果有两点值得特别注意:
链接究竟是基于域名来创建,仍是不作区分全部子域名都共享一个链接,这个策略选择上值得商榷。google的测试结果测试了两种方案,看结果彷佛是单一链接性能高于多域名链接方式。之因此出现这种状况是因为网页全部的资源请求并非同一时间发出,后续发出的子域名请求若是能复用以前的tcp链接固然性能更好。实际应用场景下应该也是单链接共享模式表现好。
测试基于两种带宽环境,一慢一快。网速快的环境下对减少延迟的提高更大,单链接模式下能够提高至60%。缘由也比较简单,带宽越大,复用链接的请求完成越快,因为三次握手和慢启动致使的延迟损耗就变得更明显。
出了链接模式和带宽以外,丢包率和RTT也是须要测试的参数。SPDY对header的压缩有80%以上,总体包大小能减小大概40%,发送的包越少,天然受丢包率影响也就越小,因此丢包率大的恶劣环境下SPDY反而更能提高体验。下图是受丢包率影响的测试结果,丢包率超过2.5%以后就没有提高了:
[图8]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/spdy_loss.png?raw=true" width="586">
RTT越大,延迟会越大,在高RTT的场景下,因为SPDY的request是并发进行的,全部对包的利用率更高,反而能更明显的减少整体延迟。测试结果以下:
[图9]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/spdy_rtt.png?raw=true" width="585">
SPDY从2012年诞生到2016中止维护,时间跨度对于网络协议来讲其实很是之短。若是HTTP2.0没有出来,google或许能收集到更多业界产品的真实反馈和数据,毕竟google本身的测试环境相对简单。但SPDY也完成了本身的使命,做为一向扮演拓荒者角色的google应该也早就预见了这样的结局。SPDY对产品网络体验的提高到底如何,恐怕只有各大厂产品经理才清楚了。
SPDY的诞生和表现说明了两件事情:一是在现有互联网设施基础和http协议普遍使用的前提下,是能够经过修改协议层来优化http1.x的。二是针对http1.x的修改确实效果明显并且业界反馈很好。正是这两点让IETF(Internet Enginerring Task Force)开始正式考虑制定HTTP2.0的计划,最后决定以SPDY/3为蓝图起草HTTP2.0,SPDY的部分设计人员也被邀请参与了HTTP2.0的设计。
HTTP2.0与SPDY的起点不一样,SPDY能够说是google的“玩具”,最先出如今自家的chrome浏览器和server上,好很差玩以及别人会不会跟着一块儿玩对google来讲无关痛痒。但HTTP2.0做为业界标准还没出生就是众人瞩目的焦点,一开始若是有什么瑕疵或者不兼容的问题影响可能又是数十年之久,因此考虑的问题和角度要很是之广。咱们来看下HTTP2.0一些重要的设计前提:
客户端向server发送request这种基本模型不会变。
使用http1.x的客户端和服务器能够无缝的经过代理方式转接到http2.0上。
不识别http2.0的代理服务器能够将请求降级到http1.x。
由于客户端和server之间在确立使用http1.x仍是http2.0以前,必需要要确认对方是否支持http2.0,因此这里必需要有个协商的过程。最简单的协商也要有一问一答,客户端问server答,即便这种最简单的方式也多了一个RTT的延迟,咱们之因此要修改http1.x就是为了下降延迟,显然这个RTT咱们是没法接受的。google制定SPDY的时候也遇到了这个问题,他们的办法是强制SPDY走https,在SSL层完成这个协商过程。ssl层的协商在http协议通讯以前,因此是最适合的载体。google为此作了一个tls的拓展,叫NPN(Next Protocol Negotiation),从名字上也能够看出,这个拓展主要目的就是为了协商下一个要使用的协议。HTTP2.0虽然也采用了相同的方式,不过HTTP2.0通过激烈的讨论,最终仍是没有强制HTTP2.0要走ssl层,大部分浏览器厂商(除了IE)却只实现了基于https的2.0协议。HTTP2.0没有使用NPN,而是另外一个tls的拓展叫ALPN(Application Layer Protocol Negotiation)。SPDY也打算从NPN迁移到ALPN了。
各浏览器(除了IE)之因此只实现了基于SSL的HTTP2.0,另外一个缘由是走SSL请求的成功率会更高,被SSL封装的request不会被监听和修改,这样网络中间的网络设备就没法基于http1.x的认知去干涉修改request,http2.0的request若是被意外的修改,请求的成功率天然会降低。
HTTP2.0协议没有强制使用SSL是由于听到了不少的反对声音,毕竟https和http相比,在不优化的前提下性能差了很多,要把https优化到几乎不增长延迟的程度又须要花费很多力气。IETF面对这种两难的处境作了妥协,但大部分浏览器厂商(除了IE)并不买账,他们只认https2.0。对于app开发者来讲,他们能够坚持使用没有ssl的http2.0,不过要承担一个多余的RTT延迟和请求可能被破坏的代价。
HTTP2.0做为新版协议,改动细节必然不少,不过对应用开发者和服务提供商来讲,影响较大的就几点。
http1.x诞生的时候是明文协议,其格式由三部分组成:start line(request line或者status line),header,body。要识别这3部分就要作协议解析,http1.x的解析是基于文本。基于文本协议的格式解析存在自然缺陷,文本的表现形式有多样性,要作到健壮性考虑的场景必然不少,二进制则不一样,只认0和1的组合。基于这种考虑http2.0的协议解析决定采用二进制格式,实现方便且健壮。
有人可能会以为基于文本的http调试方便不少,像firebug,chrome,charles等很多工具均可以即时调试修改请求。实际上如今不少请求都是走https了,要调试https请求必须有私钥才行。http2.0的绝大部分request应该都是走https,因此调试方便没法做为一个有力的考虑因素了。curl,tcpdump,wireshark这些工具会更适合http2.0的调试。
http2.0用binary格式定义了一个一个的frame,和http1.x的格式对好比下图:
[图10]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/http1xvs2.png?raw=true" width="455">
http2.0的格式定义更接近tcp层的方式,这张二机制的方式十分高效且精简。length定义了整个frame的开始到结束,type定义frame的类型(一共10种),flags用bit位定义一些重要的参数,stream id用做流控制,剩下的payload就是request的正文了。
虽然看上去协议的格式和http1.x彻底不一样了,实际上http2.0并无改变http1.x的语义,只是把原来http1.x的header和body部分用frame从新封装了一层而已。调试的时候浏览器甚至会把http2.0的frame自动还原成http1.x的格式。具体的协议关系能够用下图表示:
[图11]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/http2_frame.png?raw=true" width="455">
http2.0要解决的一大难题就是多路复用(MultiPlexing),即链接共享。上面协议解析中提到的stream id就是用做链接共享机制的。一个request对应一个stream并分配一个id,这样一个链接上能够有多个stream,每一个stream的frame能够随机的混杂在一块儿,接收方能够根据stream id将frame再归属到各自不一样的request里面。
前面还提到过链接共享以后,须要优先级和请求依赖的机制配合才能解决关键请求被阻塞的问题。http2.0里的每一个stream均可以设置又优先级(Priority)和依赖(Dependency)。优先级高的stream会被server优先处理和返回给客户端,stream还能够依赖其它的sub streams。优先级和依赖都是能够动态调整的。动态调整在有些场景下颇有用,假想用户在用你的app浏览商品的时候,快速的滑动到了商品列表的底部,但前面的请求先发出,若是不把后面的请求优先级设高,用户当前浏览的图片要到最后才能下载完成,显然体验没有设置优先级好。同理依赖在有些场景下也有妙用。
前面提到过http1.x的header因为cookie和user agent很容易膨胀,并且每次都要重复发送。http2.0使用encoder来减小须要传输的header大小,通信双方各自cache一份header fields表,既避免了重复header的传输,又减少了须要传输的大小。高效的压缩算法能够很大的压缩header,减小发送包的数量从而下降延迟。
这里普及一个小知识点。如今你们都知道tcp有slow start的特性,三次握手以后开始发送tcp segment,第一次能发送的没有被ack的segment数量是由initial tcp window大小决定的。这个initial tcp window根据平台的实现会有差别,但通常是2个segment或者是4k的大小(一个segment大概是1500个字节),也就是说当你发送的包大小超过这个值的时候,要等前面的包被ack以后才能发送后续的包,显然这种状况下延迟更高。intial window也并非越大越好,太大会致使网络节点的阻塞,丢包率就会增长,具体细节能够参考IETF这篇文章。http的header如今膨胀到有可能会超过这个intial window的值了,因此更显得压缩header的重要性。
SPDY/2使用的是gzip压缩算法,但后来出现的两种攻击方式BREACH和CRIME使得即便走ssl的SPDY也能够被破解内容,最后综合考虑采用的是一种叫HPACK的压缩算法。这两个漏洞和相关算法能够点击连接查看更多的细节,不过这种漏洞主要存在于浏览器端,由于须要经过javascript来注入内容并观察payload的变化。
不少app客户端都有取消图片下载的功能场景,对于http1.x来讲,是经过设置tcp segment里的reset flag来通知对端关闭链接的。这种方式会直接断开链接,下次再发请求就必须从新创建链接。http2.0引入RST_STREAM类型的frame,能够在不断开链接的前提下取消某个request的stream,表现更好。
Server Push的功能前面已经提到过,http2.0能经过push的方式将客户端须要的内容预先推送过去,因此也叫“cache push”。另外有一点值得注意的是,客户端若是退出某个业务场景,出于流量或者其它因素须要取消server push,也能够经过发送RST_STREAM类型的frame来作到。
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的部署出了问题。
tcp协议优化的一个经典场景是:Nagle算法和Berkeley的delayed ack算法的对立。http2.0并无对tcp层作任何修改,因此这种对立致使的高延迟问题依然存在。要么经过TCP_NODELAY禁用Nagle算法,要么经过TCP_QUICKACK禁用delayed ack算法。貌似http2.0官方建议是设置TCP_NODELAY。
HTTP2.0使用了tls的拓展ALPN来作协议升级,除此以外加密这块还有一个改动,HTTP2.0对tls的安全性作了近一步增强,经过黑名单机制禁用了几百种再也不安全的加密算法,一些加密算法可能还在被继续使用。若是在ssl协商过程中,客户端和server的cipher suite没有交集,直接就会致使协商失败,从而请求失败。在server端部署http2.0的时候要特别注意这一点。
SPDY和HTTP2.0之间的暧昧关系,以及google做为SPDY的创造者,这两点很容易让阴谋论者怀疑google是否会成为协议的最终收益方。这实际上是废话,google固然会受益,任何新协议使用者都会从中受益,至于谁吃肉,谁喝汤看的是本身的本事。从整个协议的变迁史也能够粗略看出,新协议的诞生彻底是针对业界现存问题对症下药,并无google业务相关的痕迹存在,google至始至终只扮演了一个角色:you can you up。
HTTP2.0不会是万金油,但抹了也不会有反作用。HTTP2.0最大的亮点在于多路复用,而多路复用的好处只有在http请求量大的场景下才明显,因此有人会以为只适用于浏览器浏览大型站点的时候。这么说其实没错,但http2.0的好处不只仅是multiplexing,请求压缩,优先级控制,server push等等都是亮点。对于内容型移动端app来讲,好比淘宝app,http请求量大,多路复用仍是能产生明显的体验提高。多路复用对延迟的改变能够参考下这个测试网址。
HTTP2.0对于ssl的依赖使得有些开发者望而生畏。很多开发者对ssl还停留在高延迟,CPU性能损耗,配置麻烦的印象中。其实ssl于http结合对性能的影响已经能够优化到忽略的程度了,网上也有很多文章能够参考。HTTP2.0也能够不走ssl,有些场景确实可能不适合https,好比对代理服务器的cache依赖,对于内容安全性不敏感的get请求能够经过代理服务器缓存来优化体验。
HTTP2.0做为新版本的网络协议确定须要一段时间去普及,但HTTP自己属于应用层协议,和当年的网络层协议IPV6不一样,离底层协议越远,对网络基础硬件设施的影响就越小。HTTP2.0甚至还特地的考虑了与HTTP1.x的兼容问题,只是在HTTP1.x的下面作了一层framing layer,更使得其普及的阻力变小。因此不出意外,HTTP2.0的普及速度可能会远超大部分人的预期。
Firefox 2015年在其浏览器流量中检测到,有13%的http流量已经使用了http2.0,27%的https也是http2.0,并且还处于持续的增加当中。通常用户察觉不到是否使用了http2.0,不过能够装这样一个插件,安装以后若是网站是http2.0的,在地址栏的最右边会有个闪电图标。还可使用这个网站来测试。对于开发者来讲,能够经过Web Developer的Network来查看协议细节,以下图:
[图12]
<img src="https://github.com/music4kid/music4kid.github.io/blob/master/images/firefox_http2.png?raw=true" width="552">
其中Version:HTTP/2.0已经很明确代表协议类型,Firefox还在header里面插入了X-Firefox-Spdy:“h2”,也能够看出是否使用http2.0。
Chrome在2015年检测到的http2.0流量大概有18%。不过这个数字原本会更高,由于Chrome如今很大一部分流量都在试验QUIC(google正在开辟的另外一块疆土)。Chrome上也可使用相似的插件来判断网站是不是使用http2.0。
iOS系统是从iOS8开始才经过NSURLSession来支持SPDY的,iOS9+开始自动支持http2.0。实际上apple对http2.0很是有信心,推广力度也很大。新版本ATS机制默认使用https来进行网络传输。APN(Apple Push Notifiction)在iOS9上也已是经过http2.0来实现的了。iOS9 sdk里的NSURLSession默认使用http2.0,并且对开发者来讲是彻底透明的,甚至没有api来知道究竟是用的哪一个版本的http协议。
对于开发者来讲到底怎么去配置最佳的http使用方案呢?在我看来,因app而异,主要从两方面来考虑:一是app自己http流量是否大并且密集,二是开发团队自己的技术条件。http2.0的部署相对容易不少,客户端开发者甚至不用作什么改动,只须要使用iOS9的SDK编译便可,但缺点是http2.0只能适用于iOS9的设备。SPDY的部署相对麻烦一些,但优势是能够兼顾iOS6+的设备。iOS端的SPDY可使用twitter开发的CocoaSPDY方案,但有一点须要特别处理:
因为苹果的TLS实现不支持NPN,因此经过NPN协商使用SPDY就没法经过默认443端口来实现。有两种作法,一是客户端和server同时约定好使用另外一个端口号来作NPN协商,二是server这边经过request header智能判断客户端是否支持SPDY而越过NPN协商过程。第一种方法会简单一点,不过须要从框架层将全部的http请求都map到另外一个port,url mapping能够参考我以前的一篇文章。twitter本身的网站twitter.com使用的是第二种方法。
浏览器端(好比Chrome),server端(好比nginx)都陆续打算放弃支持spdy了,毕竟google官方都宣布要中止维护了。spdy会是一个过渡方案,会随着iOS9的普及会逐步消失,因此这部分的技术投入须要开发团队本身去衡量。
android和iOS状况相似,http2.0只能在新系统下支持,spdy做为过渡方案仍然有存在的必要。
对于使用webview的app来讲,须要基于chrome内核的webview才能支持spdy和http2.0,而android系统的webview是从android4.4(KitKat)才改为基于chrome内核的。
对于使用native api调用的http请求来讲,okhttp是同时支持spdy和http2.0的可行方案。若是使用ALPN,okhttp要求android系统5.0+(实际上,android4.4上就有了ALPN的实现,不过有bug,知道5.0才正式修复),若是使用NPN,能够从android4.0+开始支持,不过NPN也是属于将要被淘汰的协议。
以上是HTTP从1.x到SPDY,再到HTTP2.0的一些主要变迁技术点。HTTP2.0正处于逐步应用到线上产品和服务的阶段,能够预见将来会有很多新的坑产生和与之对应的优化技巧,HTTP1.x和SPDY也将在一段时间内继续发挥余热。做为工程师,须要了解这些协议背后的技术细节,才能打造高性能的网络框架,从而提高咱们的产品体验。