HTTP2
HTTP2 相比较于 HTTP1.X 大幅度的提升了 web 性能, 在于 HTTP1.1 完全兼容的基础上, 进一步减少了网络延迟, 而对于前端开发人员来说, 无疑减少了在前端方面的优化工作.
具体的文档在这里: HTTP/2: the Future of the Internet
技术方案在这里: RFC 7541
多路复用
众所周知,在 HTTP/1.1 协议中, 浏览器客户端在同一时间. 针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞.
HTTP2 的多路复用则允许通过单一的 HTTP2 链接发起多重的请求-响应的消息.

因此 HTTP2 可以很容易的实现多流并行而不用依赖建立多个 TCP 链接, HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧, 这些帧对应逻辑流中的消息, 并行的在同一个 TCP 连接上双向交换消息.
二进制分帧
HTTP2 在兼容 HTTP1.X 的情况下, 在应用层(HTTP/2)和传输层(TCP/UDP)之间增加了一个二进制分帧层.

在这个二进制分帧层中, HTTP2 会将所有传输的信息分割为更小的消息和帧(frame), 并对他们采用二进制格式的编码, 其中 HTTP1.x 的首部信息会被封装到HEADER frame, 而相应的 Request Body 则封装到 DATA frame 里面.
HTTP/2 通信都在一个连接上完成, 这个连接可以承载任意数量的双向数据流.
在 1.X 版本中, HTTP 性能优化的关键并不在于高带宽, 而是低延迟. TCP 连接会随着时间进行自我调节, 期初会限制连接的最大速度, 如果数据传输成功, 会随着时间的推移提高传输的速度. 这种调谐被称为 TCP 慢启动.
HTTP2 通过让所有数据流共用一个连接, 可以更有效的使用 TCP 连接, 让高带宽服务于 HTTP 的性能提升.
总之:
- 单连接多资源的方式, 减少了服务端的压力, 内存占用更少, 连接吞吐量更大
- 由于 TCP 的连接的减少而使用网络拥塞状况得以改善, 同时慢启动时间的减少, 使得拥塞和丢包恢复速度更快.
请求优先级
把HTTP消息分为很多独立帧之后, 就可以通过优化这些帧的交错和传输顺序进一步优化性能. 每一个流都可以带一个31比特的优先值: 0表示最高优先级, 2^31 - 1表示最低优先级.
权重: 服务器可以根据流的优先级, 控制资源分配(CPU, 内存, 带宽), 而在响应数据准备好之后, 优先级将最高优先级的帧发送给客户端. 高优先级的流会优先发送. 但也不是绝对的, 因为会引入首队阻塞的问题: 高优先级的请求导致阻塞其他资源的交付.
分配处理资源和客户端与服务器之间的带宽, 不同优先级的混合也是必须的. 客户端可以指定哪个流是重要的.
流依赖关系: 每个流都可以明确的依赖另一个流, 客户端会使用权重和流依赖关系的组合信息, 向服务端构造和传递"优先级树". 这个树表明了其希望如何接受响应. 即我们期望优先级越高的请求越快得到响应.
浏览器中有一个默认的优先级。浏览器基于自身对资源重要性的判读:
- 优先级最高: html
- 优先级高: css
- 优先级中: js
- 优先级低: 图片等其他资源
首部压缩
HTTP1.X 不支持首部压缩, 因此 SPDY 和 HTTP2 应运而生, SPDY 使用的是 DEFLATE 算法, HTTP2 则使用了专门的 HPACK 算法. 减少了 header 的大小。并在两端维护了索引表,用于记录出现过的 header ,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值。
为什么要压缩
在HTTP/1 中, HTTP请求和响应都是由状态行, 请求/响应头部, 消息主题 三部分组成的. 一般而言, 消息主体都会经过gzip压缩, 或者本身传输的就是压缩过后的二进制文件(比如图片, 视频), 但状态行和头部却没有经过任何压缩, 而直接以纯文本传输
随着web功能越来越复杂, 每个页面产生的请求数量也越来越多. 根据HTTP Archive的统计, 当前平均每个页面都会产生上百个请求. 越来越多的请求导致消耗的头部的流量越来越多.
在HTTP/1时代, 为了减少头部消耗的流量, 有很多优化方案可以尝试, 例如合并请求, 启用Cookie-Free域名等等, 但是这些方案多少有点问题.
技术原理

通俗的来说, 头部压缩需要在支持HTTP/2的浏览器和服务端之间:
- 维护一份相同的静态字典(Static Table), 包含常见的头部名称, 以及特别常见的头部名称和值的组合
- 维护一份相同的动态字典(Dynamic Table), 可以动态的添加内容
- 支持基于静态哈夫曼码表的哈夫曼编码(Huffman Coding)
静态字典的作用有两个:
- 对于完全匹配的头部键值对, 例如: method: GET, 可以直接使用一个字符表示
- 对于头部名称可以匹配的键值对, 例如cookie: xxxx, 可以将名称使用一个字符表示.
HTTP/2中的静态字典大致如下(https://httpwg.org/specs/rfc7541.html#static.table.definition):
| Index | Header Name | Header Value |
|---|---|---|
| 1 | :authority | |
| 2 | :method | GET |
| 3 | :method | POST |
| 4 | :path | / |
| 5 | :path | /index.html |
| 6 | :scheme | http |
| 7 | :scheme | https |
| 8 | :status | 200 |
| … | … | … |
| 32 | cookie | |
| … | … | … |
| 60 | via | |
| 61 | www | -authenticate |
同时, 浏览器可以告知服务端, 将cookie: xxx添加到动态字典中, 这样后续整个键值对就可以使用一个字段表示了. 类似的, 服务端也可以更新对方的动态字典. 需要注意的是, 动态字典是上下文有关的, 需要为每个HTTP/2连接维护不同的字典.
使用字典可以极大的提升压缩效果, 其中静态字典在首次请求中就可以使用. 对于静态, 动态字典不存在的内容, 还可以使用哈夫曼编码来减小体积. HTTP/2使用了一份静态哈夫曼码表, 也需要内置在客户端和服务端之中.
此外,HTTP/1 的状态行信息(Method、Path、Status 等),在 HTTP/2 中被拆成键值对放入头部(冒号开头的那些),同样可以享受到字典和哈夫曼压缩。另外,HTTP/2 中所有头部名称必须小写。
服务端推送
在 HTTP 2.0 中,服务端可以在客户端某个请求后,主动推送其他资源。

可以想象以下情况,某些资源客户端是一定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间。当然在浏览器兼容的情况下你也可以使用prefetch。
服务端推送还有一个比较大的优势: 可以缓存. 也就是说可以在遵循同源策略的情况下, 不同页面之间共享缓存资源.
注意, 服务端推送有两点:
- 推送是遵循同源策略的
- 这种服务端的推送是基于客户端的请求响应来决定的.
当服务端需要主动推送某个资源的时候, 就会发送一个Frame Type为PUSH_PROMISE的Frame, 里面携带了PUSH需要新建的Stream ID. 意思是告诉客户端: 接下来我要用这个ID给你发送信息. 客户端解析Frame的时候, 发现它是一个PUSH_PROMISE类型, 就会接受服务端要推送的流.
性能瓶颈
启用HTTP/2会带来很大的性能提升, 但是也会带来新的性能瓶颈. 因为现在所有的压力都集中在底层一个TCP的连接上, TCP很可能就是下一个性能瓶颈, 比如TCP分组的队首阻塞问题, 单个TCP packet丢失导致的整个连接阻塞, 无法避免, 此时所有消息都会受到印象.
nginx 升级 HTTP2
- nginx版本高于1,9,5
- 安装了
--with-http_ssl_module和--with-http_v2_module - 配置了HTTPS(启用HTTP2的前提条件)
- 配置
listen 443 ssl http2 nginx restart(注意不要直接nginx -s reload)