在我们所处的互联网世界中,HTTP协议算得上是使用最广泛的网络协议。最近http2.0的诞生使得它再次互联网技术圈关注的焦点。任何事物的消退和新生都有其背后推动的力量。对于HTTP来说,这力量复杂来说是各种技术细节的演进,简单来说是用户体验和感知的进化。用户总是希望网络上的信息能尽可能快的抵达眼球,越快越好,正是这种对“快”对追逐催生了今天的http2.0。
1. HTTP2.0的前世
http2.0的前世是http1.0和http1.1这两兄弟。虽然之前仅仅只有两个版本,但这两个版本所包含的协议规范之庞大,足以让任何一个有经验的工程师为之头疼。http1.0诞生于1996年,协议文档足足60页。之后第三年,http1.1也随之出生,协议文档膨胀到了176页。不过和我们手机端app升级不同的是,网络协议新版本并不会马上取代旧版本。实际上,1.0和1.1在之后很长的一段时间内一直并存,这是由于网络基础设施更新缓慢所决定的。今天的http2.0也是一样,新版协议再好也需要业界的产品锤炼,需要基础设施逐年累月的升级换代才能普及。
1.1 HTTP站在TCP之上
理解http协议之前一定要对TCP有一定基础的了解。HTTP是建立在TCP协议之上,TCP协议作为传输层协议其实离应用层并不远。HTTP协议的瓶颈及其优化技巧都是基于TCP协议本身的特性。比如TCP建立连接时三次握手有1.5个RTT(round-trip time)的延迟,为了避免每次请求的都经历握手带来的延迟,应用层会选择不同策略的http长链接方案。又比如TCP在建立连接的初期有慢启动(slow start)的特性,所以连接的重用总是比新建连接性能要好。
1.1 HTTP应用场景
http诞生之初主要是应用于web端内容获取,那时候内容还不像现在这样丰富,排版也没那么精美,用户交互的场景几乎没有。对于这种简单的获取网页内容的场景,http表现得还算不错。但随着互联网的发展和web2.0的诞生,更多的内容开始被展示(更多的图片文件),排版变得更精美(更多的css),更复杂的交互也被引入(更多的js)。用户打开一个网站首页所加载的数据总量和请求的个数也在不断增加。今天绝大部分的门户网站首页大小都会超过2M,请求数量可以多达100个。另一个广泛的应用是在移动互联网的客户端app,不同性质的app对http的使用差异很大。对于电商类app,加载首页的请求也可能多达10多个。对于微信这类IM,http请求可能仅限于语音和图片文件的下载,请求出现的频率并不算高。
1.2 因为延迟,所以慢
影响一个网络请求的因素主要有两个,带宽和延迟。今天的网络基础建设已经使得带宽得到极大的提升,大部分时候都是延迟在影响响应速度。http1.0被抱怨最多的就是连接无法复用,和head of line blocking这两个问题。理解这两个问题有一个十分重要的前提:客户端是依据域名来向服务器建立连接,一般PC端浏览器会针对单个域名的server同时建立6~8个连接,手机端的连接数则一般控制在4~6个。显然连接数并不是越多越好,资源开销和整体延迟都会随之增大。
连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。
head of line blocking会导致带宽无法被充分利用,以及后续健康请求被阻塞。假设有5个请求同时发出,如下图:
对于http1.0的实现,在第一个请求没有收到回复之前,后续从应用层发出的请求只能排队,请求2,3,4,5只能等请求1的response回来之后才能逐个发出。网络通畅的时候性能影响不大,一旦请求1的request因为什么原因没有抵达服务器,或者response因为网络阻塞没有及时返回,影响的就是所有后续请求,问题就变得比较严重了。
1.3 解决连接无法复用
http1.0协议头里可以设置Connection:Keep-Alive。在header里设置Keep-Alive可以在一定时间内复用连接,具体复用时间的长短可以由服务器控制,一般在15s左右。到http1.1之后Connection的默认值就是Keep-Alive,如果要关闭连接复用需要显式的设置Connection:Close。一段时间内的连接复用对PC端浏览器的体验帮助很大,因为大部分的请求在集中在一小段时间以内。但对移动app来说,成效不大,app端的请求比较分散且时间跨度相对较大。所以移动端app一般会从应用层寻求其它解决方案,长连接方案或者伪长连接方案:
方案一:基于tcp的长链接
现在越来越多的移动端app都会建立一条自己的长链接通道,通道的实现是基于tcp协议。基于tcp的socket编程技术难度相对复杂很多,而且需要自己制定协议,但带来的回报也很大。信息的上报和推送变得更及时,在请求量爆发的时间点还能减轻服务器压力(http短连接模式会频繁的创建和销毁连接)。不止是IM app有这样的通道,像淘宝这类电商类app都有自己的专属长连接通道了。现在业界也有不少成熟的方案可供选择了,google的protobuf就是其中之一。
方案二:http long-polling
long-polling可以用下图表示:
客户端在初始状态就会发送一个polling请求到服务器,服务器并不会马上返回业务数据,而是等待有新的业务数据产生的时候再返回。所以连接会一直被保持,一旦结束马上又会发起一个新的polling请求,如此反复,所以一直会有一个连接被保持。服务器有新的内容产生的时候,并不需要等待客户端建立一个新的连接。做法虽然简单,但有些难题需要攻克才能实现稳定可靠的业务框架:
- 和传统的http短链接相比,长连接会在用户增长的时候极大的增加服务器压力
- 移动端网络环境复杂,像wifi和4g的网络切换,进电梯导致网络临时断掉等,这些场景都需要考虑怎么重建健康的连接通道。
- 这种polling的方式稳定性并不好,需要做好数据可靠性的保证,比如重发和ack机制。
- polling的response有可能会被中间代理cache住,要处理好业务数据的过期机制。
long-polling方式还有一些缺点是无法克服的,比如每次新的请求都会带上重复的header信息,还有数据通道是单向的,主动权掌握在server这边,客户端有新的业务请求的时候无法及时传送。
方案三:http streaming
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年才起草,并不是所有的浏览器都提供了支持。各大浏览器厂商最新的版本都提供了支持。
1.4 解决head of line blocking
Head of line blocking(以下简称为holb)是http2.0之前网络体验的最大祸源。正如图1中所示,健康的请求会被不健康的请求影响,而且这种体验的损耗受网络环境影响,出现随机且难以监控。为了解决holb带来的延迟,协议设计者设计了一种新的pipelining机制。
http pipelining
pipelining的流程图可以用下图表示:
和图一相比最大的差别是,请求2,3,4,5不用等请求1的response返回之后才发出,而是几乎在同一时间把request发向了服务器。2,3,4,5及所有后续共用该连接的请求节约了等待的时间,极大的降低了整体延迟。下图可以清晰的看出这种新机制对延迟的改变:
不过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的老服务器协商有问题。
- 可