简介
在很多前端性能优化文章中都提到http2和quic的重要作用,那么什么是http2/quic?能够优化性能的原理是什么?实际中如何使用?我们今天就来讨论一下这些问题。
http有几个历史版本,前期迭代是为了增强功能,后期版本升级主要围绕性能在做优化。
http/0.9(1991)、http/1.0(1996)、http/1.1(1999)、spdy(2009)、http/2(2015)、http/3(2016)(quic)。
http
http,超文本传输协议(Hyper Text Transfer Protocol),与之对应的是HTML,超文本标记语言(Hyper Text Markup Language),超文本是一种组织信息的方式,它通过超级链接方法将文本中的文字、图表与其他信息媒体相关联。HTML增强了普通文本的表现力,增强了用户浏览信息的体验。
http发展至今经历了几个版本。
http/0.9
http最初设计出来是为了传输HTML文件,http0.9及其简单,请求方法是
GET /index.html
服务端响应是
(response body)
(connection closed)
http0.9没有头信息,仅支持GET,只支持HTML。
http/1.0
http1.0相对于http0.9有很大改进
- 支持多种格式文件,图片、视频、文本等
- 支持多种请求方法,POST、HEAD等
- 请求和响应增加了头信息
- 引入状态码定义响应的特征
- 缓存
- 支持多段类型,在 RFC 2387 文件中,指出若要传输多种参数,多种资料型态混合的信息时,要先将 HTTP 要求的 Content-Type 设为 multipart/form-data,而且要设定一个 boundary 参数,这个参数是由应用程序自行产生,它会用来识别每一份资料的边界 (boundary),用以产生多重信息部份 (message part)
http/1.1
http/1.1对http/1.0一些问题做了优化
- 增加了更多请求方法,PUT、OPTIONS、DELETE
- 范围请求,在请求头和响应头中增加数据范围相关信息,可以指定读写数据的范围。这个功能是范围请求(比如通过http传输的音视频在seek时候可能需要请求某个范围的数据)、多线程下载、断点续传等能力的前提。
- 持久连接keep alive,多个http请求可以复用同一个TCP连接,要关闭该连接只需要在头信息加入“Connection: close”这样省去TCP三次握手的性能损耗。
- 管线化支持,keep-alive虽然能够让多个请求复用同一个TCP连接,但会限制一个请求的响应返回后才能继续下一次请求,如果前面请求和响应比较慢,将会阻塞后续请求,为了避免这种“队头阻塞”问题,http/1.1引入了管线化机制,让多个http请求可以并行发出,不需要等待上一次的响应结束。但管线化也有限制,要求服务器按照发送请求的顺序处理请求,并且客户端需要按照请求顺序接收响应。这样如果服务器处理某个请求时间比较长,将会阻塞后续请求的处理和返回响应。
http/2
在2009年,为了提升网页性能和安全性,谷歌提出SPDY协议,这是处于HTTP之上的一个传输层,在请求被发送前做了一些修改。它的特点包括复用性、安全性、压缩、优先级等。当时SPDY已经被很多浏览器支持。
2015年,谷歌不希望有两个相互竞争的标准,于是将SPDY合并到HTTP中,因此SPDY废弃,HTTP/2诞生。HTTP/2主要特性都借鉴了SPDY协议。
http2为了对http1.1优化,提升性能而生。http1.1有些性能问题,包括文本传输,传输和解析比较慢;头部过大每次都要发送,浪费资源;每次建立TCP连接耗费性能,keep-alive和管线化并未完全解决队头阻塞问题。下面看http2是如何解决这些问题的。
特性
二进制传输
http/1.1及之前的版本使用文本形式的报文,这样比较直观易调试。http/2版本改为二进制数据。
原来使用纯文本的时候容易出现多义性,比如大小写、空白字符、回车换行、多字少字等等,程序在处理时必须用复杂的状态机,效率低,还麻烦。而二进制里只有“0”和“1”,可以严格规定字段大小、顺序、标志位等格式,“对就是对,错就是错”,解析起来没有歧义,实现简单,而且体积小、速度快,做到“内部提效”。 ——《透视HTTP协议》罗剑锋
简单地说,http/2把报文从文本改为二进制形式,提升了解析速度。
改为二进制报文后,http/2的消息由一个个的帧组成。HEADERS帧用来承载元数据,DATA帧承载内容。还有一些其它的帧(RST_STEAM、SETTINGS、PRIORITY)。
每个请求或者响应都被赋予一个流id,一个流id对应多个帧,每个帧中都有流id信息。客户端请求的流id是奇数,服务端的响应的流id是偶数。
头部压缩
http/1请求头中包含许多固定的头字段,如“User Agent”、“Cookie”、“Server”,可能多达几百字节,这些数据在每次请求中都会带上,http/1并未对此进行压缩优化。
http/2使用“HPACK”算法进行请求头压缩。
- 发送的头部数据使用哈夫曼编码压缩整数和字符串,可以达到50%~90%的压缩率。
- 在客户端和服务端建立“字典”,用索引号表示重复字符串。“字典”包括静态表和动态表,静态表中保存常用的key: value对,而动态表示在请求过程中建立起来的,例如首次发送请求,记录Cookie字段及其值为一个索引,后续发送时候如果值相同,就发送索引即可。
注意当网络迁移后,HPACK建立的表会销毁重建。
多路复用
http/2在同一个TPC连接中可以发送多个请求。使用http/2的话,同一个TCP连接中可能有多个流。多个流之间是无序的,因此不会出现前面请求阻塞后面请求的“队头响应”问题。每个请求或者响应都被赋予一个流id,每个帧的数据包里面带有流id,标识这个帧属于那个流。
http/2可以单独关闭一个流(发送“RST_STREAM”帧),而不会影响到TCP连接上的其他流。
chrome network中可以查看connectionId,每个TCP连接对应一个id,可以看到http1.1每个资源对应一个id,而http2则是多个资源(同一域名)对应同一个id,说明他们复用了同一个tcp连接。另外如果静态资源较多,可以发现,http1.1最多有6个资源并发请求,而http2则没有限制。
http/2的多路复用机制并未完全解决“队头阻塞”问题,因为TCP本身有丢包重传机制,有丢包的话,会一直重传直到该包成功发送,后面的包会阻塞,因此http/2的弱网表现不好,有实验表明,2%丢包时候(网络比较差)http/2的性能不如http1.1。
服务端推送
HTTP/2 还在一定程度上改变了传统的“请求 - 应答”工作模式,服务器不再是完全被动地响应请求,也可以新建“流”主动向客户端发送消息。比如,在浏览器刚请求 HTML 的时候就提前把可能会用到的 JS、CSS 文件发给客户端,减少等待的延迟,这被称为“服务器推送”(Server Push,也叫 Cache Push)。
请求优先级
流可以设置优先级,比如让服务器先处理HTML、CSS,后处理图片,提高用户体验。
安全性
所有浏览器都是只支持基于TLS的HTTP2。
应用
使用http2,文件可以更加碎片化以便并行请求。
不需要域名打散,域名打散反而不能利用http2多路复用,增加了新建TCP连接的损耗。
另外像内联js、css;雪碧图等在http1.1中通过减少请求次数来提升性能的手段,在http2中可能会浪费了并发的。
QUIC
进展
最初的QUIC协议由Jim Roskind在Google设计并于2012年实现,经过Google的扩大试验后,于2013年向全世界公开发布。 Google实现了QUIC协议,并随后将它部署在其广泛流行的浏览器(Chrome)和最流行的服务(搜索、Gmail、YouTube等等)中。 2015年6月,首个QUIC的互联网草案被提交到IETF以进行标准化,但直到2016年下半年,一个QUIC工作组才被批准成立并投入工作。随后它在各方的高度关注下迅速发展。 最初被Google发明时候,成为gQUIC,后来由IETF接管的QUIC叫做iQUIC。两者差距较大。 很多现代浏览器已经开始支持QUIC。原理
- 基于QUIC的下一个版本的http就是http/3。在性能上和弱网表现优于之前的版本。
- QUIC基于UDP,实现了可靠传输。
- QUIC并不是建立在TLS之上,而是内部包含了TLS。
- 引入了类似http/2的“流”和多路复用的机制,(在QUIC中客户端的流id是偶数,服务端的是奇数)单个流是有序的,会因丢包阻塞,以此保证可靠传输,但流之间不会相互影响。这样就解决了队头阻塞的问题。
- QPack算法进行头部压缩。
- TCP连接和ip + port强绑定,因此当网络切换(如从wifi切换到4g)时候,ip和port可能发生变化,所以需要重新建立TCP连接,而QUIC协议使用“不透明”的流id,客户端和服务端选择协议提供的一组id来标识,这样当网络切换时候可以在新ip上继续用之前的连接,避免重建连接的损耗。
通过《透视HTTP协议》中的这个图可以看出来QUIC和http/3的关系以及QUIC运行在哪个位置。