上篇文章深入浅出: 5G 和 HTTP 里给自己挖了一根深坑, 说是要写一篇关于 HTTP/2 的文章, 今天来还账了.
本文分为以下几个部分:
HTTP/2 的背景
HTTP/2 的特点
HTTP/2 的协议分析
HTTP/2 的支持
HTTP/2 简介
HTTP/2 主要是为了解决现 HTTP 1.1 性能不好的问题才出现的. 当初 Google 为了提高 HTTP 性能, 做出了 SPDY, 它就是 HTTP/2 的前身, 后来也发展成为 HTTP/2 的标准.
HTTP/2 兼容 HTTP 1.1, 例如 HTTP Method,Status code,URI 以及大部分 Header Fields.
HTTP/2 通过以下方法减少 latency, 用来改进页面加载的速度,
HTTP Header 的压缩, 采用的是 HPack 算法.
HTTP/2 的 Server Push, 非常重要的一个特性.
请求的 pipeline.
修复在 HTTP 1.x 的队头阻塞问题.
在单个 TCP 连接里多工复用请求.
HTTP/2 支持 HTTP 1.1 里的大部分 use case, 例如桌面浏览器, 移动浏览器, web API,Web Server, 代理服务器, 反向代理服务器, 防火墙和 CDN 等.
HTTP/2 头部压缩(HPack)
HPack 是 HTTP/2 里 HTTP 头压缩的算法, 具体可以参看 https://tools.ietf.org/html/rfc7541 . 下面简单介绍一下 HPack 是如何工作的.
见下图, 该图来自 Google 的性能专家 Ilya Grigorik 的文章 HTTP/2 is here, let's optimize!, 它非常直观地描述了 HTTP/2 中头部压缩的原理:
简单说, HTTP 头压缩需要在 HTTP/2 Client 和服务端之间:
维护一份相同的静态表(Static Table), 包含常见的头部名称, 以及特别常见的头部名称与值的组合;
维护一份相同的动态表(Dynamic Table), 可以动态地添加内容;
基于静态哈夫曼码表的哈夫曼编码(Huffman Coding);
在 HTTP 头里, 有些 key:value 是固定, 例如:
- :method: GET
- :scheme: http
在编码时, 它们直接用一个 index 编号代替, 例如: method:GET 是 2, 这些在一个静态表定义. 静态表的定义如下, 总共 61 个 Header Name, 点击 URL https://tools.ietf.org/html/rfc7541#appendix-A https://tools.ietf.org/html/rfc7541#appendix-A 查看所有静态表的定义.
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 | |
使用静态表, 动态表, 以及 Huffman 编码可以极大地提升压缩效果. 对于静态表里的字段, 原来需要 N 个字符表示的, 现在只需要一个索引即可, 对于静态, 动态表中不存在的内容, 还可以使用哈夫曼编码来减小体积. HTTP/2 标准里也给出了一份详细的静态哈夫曼码表( https://tools.ietf.org/html/rfc7541#appendix-B ), 它们需要内置在客户端和服务端之中.
关于 HPack 的算法和实现, 后面专门抽一篇文章来写.
HTTP/2 ALPN
HTTP/2 协议里有个 negotiation 的机制, 让客户端和服务器选择使用 HTTP 1.1 还是 2.0, 这个是由 ALPN 来实现, 关于 ALPN, 可以参看
ALPN(Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension, https://tools.ietf.org/html/rfc7301 .
下面是抓包截图, 在 TLS 里的 Client Hello 的包里, 我们可以看到 ALPN 里由 H2 和 HTTP/1.1, 这就是说客户端支持 HTTP2 以及 HTTP 1.1.
当 Server 收到后, 会识别 Client 发过来的协议列表, 如果不认识就忽略掉. 如果认识多个, 则选择一个最合适的协议发布给 Client. 也是在 Server Hello 里的 ALPN 返回, 见下图.
HTTP/2 Server Push 机制
Server Push 是 HTTP 2 最重要的一个特性.
在 HTTP 1.1 里, 在同一个 TCP 连接里面, 上一个回应 (response) 发送完了, 服务器才能发送下一个, 但在 HTTP/2 里, 可以将多个回应一起发送.
下图是 PUSH 模式, 当请求一个 HTML 时, 如果 HTML 里有 CSS 文件, server 会一并推给 client, 而不像在 HTTP 1.1 下, 还需要再发一个 CSS 的请求.
根据上图, 从理论上 PUSH 模式下性能会好很多.
举个例子解释一下. 下面是一个简单的 HTML 页面, 假说是 index.HTML .
- <HTML>
- <head>
- <link rel="stylesheet" href="style.css">
- </head>
- <body>
- <p>This is a sample to illustrate how HTTP/2 works</p>
- <img src="example.png">
- </body>
- </HTML>
这里有三个文件需要处理: 该 HTML 页面, CSS 文件 style.CSS 以及图片 example.PNG. 在 HTTP 1.1 里为了处理这三个文件, Client 需要发三个请求给 Server.
首先, 发送一个请求 index.HTML,
GET /index.HTML HTTP/1.1
Client 解析该 HTML 文件, 继而知道有 2 个 style.CSS 和 example.PNG 资源文件下载.
Client 继续发送 2 个请求下载他们.
GET /style.CSS HTTP/1.1
以及
GET /example.PNG HTTP/1.1
一般为了解决这两个问题, 像 CSS 文件, 可以把 CSS code 直接放在 HTML 里, 也可以把 example.PNG 转化为 base64 code 嵌入在 HTML 里, 以上只是把外部资源文件合并到 HTML 里.
除了上述方法, 还有一个优化的方法, 就是 Preload(预加载), 可以参看这里, https://w3c.github.io/preload/ .
所以我们可以把 HTML 代码改成如下:
- <link rel="preload" href="/styles.css" as="style">
- <link rel="preload" href="/example.png" as="image">
那 Preload 是什么意思呢? 就是说下载前一个页面时, 可以把相关的资源文件预先加载好, 这样感觉起来会快一些. 但是有一个关键问题需要注意, 即便是预加载的情况下, 也不能减少 HTTP 请求次数.
针对上面的问题, 我们引出服务器推送(server push). 根据上面的图, 我们可以看出, Server 还没有收到 Client 的请求, 就把各种资源推送给 Client.
拿上面例子继续举例, 当 Client 只请求 index.HTML, 但是 Server 把 index.HTML,style.CSS,example.PNG 全部发送给浏览器. 这样只需要一轮 HTTP 通信, Client 就得到了全部资源.
HTTP/2 的支持
现在主流的软件都支持 HTTP/2.
浏览器
基本上大部分浏览器在 2015 年底都支持 HTTP/2 了, 包括 Chrome,Opera,Firefox,IE 11,Safari,Edge.
在 Chrome 上, 可以下载插件 HTTP Indicator, 判断访问的网站是否支持 HTTP/2.
也可以打开 Chrome 的开发者工具, 打开 Network tab, 可以看到 Protocol 为 h2 的就是 HTTP/2 请求. 如果 Initiator 为 push 的, 说明开启了 Server Push 模式.
常用 Server 软件:
Apache HTTPd, 从版本 2.4.12 开始支持, 通过模块 mod_h2 来支撑.
Apache Tomcat, 从版本 8.5 开始支持.
Jetty 从 9.3 开始支持.
Netty 从 4.1 开始.
IIS 在 Win10 和 Windows Server 2016 支持.
Ngnix 从 1.9.5 开始支持 HTTP2, 但 Server Push 功能则在 1.13.9 才开始.
硬件:
Ctrix NetScaler 从 11.x 开始支持
F5 BIG-IP 从 11.6 开始.
- CDN/Cloud:
- Akamai
- AWS
- Azure
- Aliyun
- Tecent Cloud
缓存问题
如果开启了 Server Push 模式, 我们很容易意识到一个问题, 那就是缓存问题. Server 见到 HTML 页面就把外部资源 push 给 Client, 如果没有缓存, 其实很浪费. 为了解决这个问题, 可以在第一次请求时 push, 后面的请求都不 push 了.
服务器推送有一个很麻烦的问题. 所要推送的资源文件, 如果浏览器已经有缓存, 推送就是浪费带宽. 即使推送的文件版本更新, 浏览器也会优先使用本地缓存. 下面是 Nginx 官方给出的示例, 根据 Cookie 判断是否为第一次访问().
- server {
- listen 443 ssl http2 default_server;
- ssl_certificate ssl/certificate.pem;
- ssl_certificate_key ssl/key.pem;
- root /var/www/HTML;
- http2_push_preload on;
- location = /demo.HTML {
- add_header Set-Cookie "session=1";
- add_header Link $resources;
- }
- }
- map $http_cookie $resources {
- "~*session=1" "";
- default "</style.css>; as=style; rel=preload, </image1.jpg>; as=image; rel=preload, </image2.jpg>; as=image; rel=preload";
HTTP/2 的性能
有人专门做过测试,, 借用该文的一张图片,
可以看出, 也并未提高多少. 如果使用不当, 反而会使性能下降.
另外, ngnix 专门撰文描述 7 个提高 HTTP/2 的技巧 .
参考文章:
- https://en.wikipedia.org/wiki/HTTP/2
- https://tools.ietf.org/html/rfc7301
- https://tools.ietf.org/html/rfc7541 (HPack)
- https://w3c.github.io/preload/
来源: https://www.cnblogs.com/confach/p/10141273.html