parent
5de5639050
commit
2eb6942a9e
@ -0,0 +1,55 @@ |
|||||||
|
--- |
||||||
|
HTTP 进化史 |
||||||
|
--- |
||||||
|
|
||||||
|
### 目录 |
||||||
|
|
||||||
|
1. HTTP/0.9 和 HTTP/1.0 |
||||||
|
2. HTTP/1.1 |
||||||
|
3. SPDY 和 HTTP/2 |
||||||
|
|
||||||
|
### HTTP/0.9 和 HTTP/1.0 |
||||||
|
|
||||||
|
HTTP/0.9 是个相当简单的协议,它只有一个 GET 方法,没有首部,其设计目标就是获取 HTML(其实也就是文本,没有图片) |
||||||
|
|
||||||
|
在 1996 年通过 RFC 1945 制定了 HTTP/1.0 规范。1.0 版本为原有的轻量协议新增了大量内容,它包含了一些我们如今非常熟悉的概念: |
||||||
|
|
||||||
|
* 首部 |
||||||
|
* 响应码 |
||||||
|
* 重定向 |
||||||
|
* 错误 |
||||||
|
* 条件请求 |
||||||
|
* 内容编码(压缩) |
||||||
|
* 更多的请求方法 |
||||||
|
|
||||||
|
尽管相对于 0.9 版本来说,HTTP/1.0 是一个巨大的飞跃,但仍存在很多瑕疵,尤其是不能让多个请求共用一个连接,缺少强制的 Host 首部,并且缓存的选择也相当简陋,这三点影响了 Web 可扩展的方式。 |
||||||
|
|
||||||
|
### HTTP/1.1 |
||||||
|
|
||||||
|
1.0 版本刚刚制定,1.1 版本就接憧而来。截至目前,1.1 版本的协议已经使用了 20 多年了,它修复了之前提到的 1.0 版本的大量问题。因为强制要求客户端提供 Host 首部,所以虚拟主机托管成为可能,也就是在一个 IP 上提供多个 Web 服务。当使用新的连接指令时,Web 服务器也不需要在每个响应之后关闭连接。这对于提升性能和效率而言意义重大,因为浏览器再也不用为每个请求重新发起 TCP 连接了。 |
||||||
|
|
||||||
|
添加的变更如下: |
||||||
|
|
||||||
|
* 缓存相关首部的扩展 |
||||||
|
* OPTIONS 方法 |
||||||
|
* Upgrade 首部 |
||||||
|
* Range 请求 |
||||||
|
* 压缩和传输编码 |
||||||
|
* 管道化 |
||||||
|
|
||||||
|
管道化这种特性允许客户端一次发送所有的请求。但是有些问题阻塞了管道化的普及,服务器仍然只能按顺序响应请求。具体来说,如果某个请求花了很长时间,那么队头阻塞会影响其他请求。 |
||||||
|
|
||||||
|
### SPDY 和 HTTP/2 |
||||||
|
|
||||||
|
2009 年,Google 提出了 SPDY,SPDY 不是第一个希望替代 HTTP 的方案,但它是其中最重要的一个,因为它带来了显而易见的性能提升。 |
||||||
|
|
||||||
|
IETF 决定使用 SPDY 作为 HTTP/2.0 的起点,最终在 2015 年发布 HTTP/2.0 成为正式协议。 |
||||||
|
|
||||||
|
HTTP/2.0 被寄予了如下期望: |
||||||
|
|
||||||
|
* 相比于使用 TCP 的 HTTP/1.1,最终用户可感知的多数延迟都有能够量化的显著改善 |
||||||
|
* 解决了 HTTP 中的队头阻塞问题 |
||||||
|
* 并行的实现机制不依赖与服务器建立多个连接,从而提升 TCP 连接的利用率,特别是在拥塞控制方面 |
||||||
|
* 保留 HTTP/1.1 的语义,可以利用已有的文档资源,包括 HTTP 方法、状态码、URI 和首部字段 |
||||||
|
* 明确定义 HTTP/2.0 和 HTTP/1.x 交互的方法 |
||||||
|
* 明确指出它们可以被合理使用的新的扩展点和策略 |
@ -0,0 +1,99 @@ |
|||||||
|
--- |
||||||
|
HTTP/2 协议 |
||||||
|
--- |
||||||
|
|
||||||
|
### 目录 |
||||||
|
|
||||||
|
1. HTTP/2 分层 |
||||||
|
2. 连接 |
||||||
|
3. 帧 |
||||||
|
4. 流 |
||||||
|
|
||||||
|
### HTTP/2 分层 |
||||||
|
|
||||||
|
HTTP/2 大致可以分为两部分:分帧层,即 h2 多路复用能力的核心部分;数据或 http 层,其中包含传统上被认为是 HTTP 及其关联数据的部分。彻底分开这两层,把它们当成彼此独立的事物。 |
||||||
|
|
||||||
|
尽管数据层被设计成可以向后兼容 HTTP/1.1,对于熟悉 h1 并习惯于阅读线上协议的开发者来说,还有些地方需要重新确认。 |
||||||
|
|
||||||
|
**二进制协议** |
||||||
|
|
||||||
|
h2 的分帧层是基于帧的二进制协议。这方便了机器解析,但是肉眼识别起来非常困难。 |
||||||
|
|
||||||
|
**首部压缩** |
||||||
|
仅仅使用二进制协议似乎还不够,h2 的首部还会被深度压缩,这将显著减少传输中的冗余字节。 |
||||||
|
|
||||||
|
**多路复用** |
||||||
|
在你喜爱的调试工具里查看基于 h2 传输的连接的时候,你会发现请求和响应交织在一起。 |
||||||
|
|
||||||
|
##### 加密传输 |
||||||
|
|
||||||
|
最重要的是,线上传输的绝大部分数据是加密过的,所以在中途读取会更加困难。 |
||||||
|
|
||||||
|
### 连接 |
||||||
|
|
||||||
|
连接是所有 HTTP/2 会话的基础元素,其定义是客户端初始化一个 TCP/IP socket,客户端是指发送 HTTP 请求的实体。这和 h1 是一样的,不过与完全无状态的 h1 不同的是,h2 把它所承载的帧和流共同依赖的连接层元素捆绑在一起,其中既包含连接层设置也包含首部表。 |
||||||
|
|
||||||
|
是否支持 h2? |
||||||
|
|
||||||
|
协议发现 --- 识别终端是否支持你想要使用的协议,会比较棘手。HTTP/2 提供两种协议发现的机制。 |
||||||
|
|
||||||
|
在连接不加密的情况下,客户端会利用 Upgrade 首部来表明期望使用 h2,如果服务器也可以支持 h2,它会返回一个 “101 Switching Protocols”(协议转换)响应,这增加了一轮完整的请求 - 响应通信。 |
||||||
|
|
||||||
|
如果连接基于 TLS,情况就不同了。客户端在 Clienthello 消息中设置 ALPN(应用层协议协商)扩展来表明期望使用 h2 协议,服务器用同样的方式回复。如果使用这种方式,那么 h2 在创建 TLS 握手的过程中完成协商,不需要多余的网络通信。 |
||||||
|
|
||||||
|
为了向服务器双重确认客户端支持 h2,客户端会发送一个叫做 connection preface(连接前奏)的魔法字节流,作为连接的第一份数据。这主要是为了应对客户端通过纯文本的 HTTP/1.1 升级上来的情况,该数据的 ASCII 为: |
||||||
|
|
||||||
|
``` |
||||||
|
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n |
||||||
|
``` |
||||||
|
|
||||||
|
这个字符串的用处是,如果服务器不支持 h2,就会产生一个显式错误。这个消息特意设计成 h1 消息的样式,如果运行良好的 h1 服务器收到这个字符串,它会阻塞这个方法(PRI)或者版本(HTTP/2.0),并返回错误,可以让 h2 客户端明确的知道发生了什么错误。 |
||||||
|
|
||||||
|
### 帧 |
||||||
|
|
||||||
|
HTTP/2 是基于帧的协议,采用分帧是为了将重要信息封装起来,让协议的解析方可以轻松阅读、解析并还原信息。相比之下,h1 不是基于帧的,而是以文本分割。 |
||||||
|
|
||||||
|
下面是一个 HTTP/2 帧的结构: |
||||||
|
|
||||||
|
![](https://i.loli.net/2019/11/11/dlngaVBcK3xAY6Q.png) |
||||||
|
|
||||||
|
前九个字节对于每个帧是一致的,解析时只需要读取这些字节,就可以准确的知道在整个帧中期望的字节数。其中每个字段的说明,如下表: |
||||||
|
|
||||||
|
| 名称 | 长度 | 描述 | |
||||||
|
| ---------------- | -------- | ---------------------------------------- | |
||||||
|
| Length | 3 字节 | 表示帧负载的长度 | |
||||||
|
| Type | 1 字节 | 当前帧类型 | |
||||||
|
| Flags | 1 字节 | 具体帧类型的标识 | |
||||||
|
| R | 1 位 | 保留位,不要设置 | |
||||||
|
| Stream Identifer | 31 位 | 每个流的唯一 ID | |
||||||
|
| Frame Payload | 长度可变 | 真实的帧内容,长度在 Length 字段中设置的 | |
||||||
|
|
||||||
|
HTTP/2 帧类型: |
||||||
|
|
||||||
|
| 名称 | ID | 描述 | |
||||||
|
| ------------- | ---- | -------------------------------------- | |
||||||
|
| DATA | 0x0 | 传输流的核心内容 | |
||||||
|
| HEADERS | 0x1 | 包含 HTTP 首部,和可选的优先级参数 | |
||||||
|
| PRIORITY | 0x2 | 指示或者更改流的优先级和依赖 | |
||||||
|
| RSY_STREAM | 0x3 | 允许一端停止流 | |
||||||
|
| SETTINGS | 0x4 | 协商连接时参数 | |
||||||
|
| PUSH_PROMISE | 0x5 | 提示客户端,服务器要推送些东西 | |
||||||
|
| PING | 0x6 | 测试连接可用性和往返时延 | |
||||||
|
| GOAWAY | 0x7 | 告诉另一端,当前端已结束 | |
||||||
|
| WINDOW_UPDATE | 0x8 | 协商一端将要接收多少字节,用于流量控制 | |
||||||
|
| CONTINUATION | 0x9 | 用以扩展 HEADER 数据库 | |
||||||
|
|
||||||
|
### 流 |
||||||
|
|
||||||
|
HTTP/2 规范对流的定义是:HTTP/2 连接上独立的、双向的帧序列交换。你可以将流看作在连接上的一系列帧,它们构成了单独的 HTTP 请求和响应。如果客户端想要发出请求,它会开启一个新的流。然后,服务器将在这个流上回复。这与 h1 的请求/响应流程类似,重要的区别在于,因为有分帧,所以多个请求和响应可以交错,而不会互相阻塞。流 ID(帧首部的第 6~9 字节)用来标识帧所属的流。 |
||||||
|
|
||||||
|
客户端到服务器的 h2 连接建立之后,通过发送 HEADERS 帧来启动新的流,如果首部需要跨多个帧,可能还会发送 CONTINUATION 帧。该 HEADERS 帧可能来自 HTTP 请求,也可能来自响应,具体取决于发送方。后续流启动的时候,会发送一个带有递增流 ID 的新的 HEADERS 帧。 |
||||||
|
|
||||||
|
> CONTINUATION 帧 |
||||||
|
> |
||||||
|
> HEADERS 帧通过在帧的 Flags 字段中设置 END_HEADERS 标识位来标识首部结束。在单个 HEADERS 帧装不下所有 HTTP 首部的情况下,不会设置 END_HEADERS 标识位,而是在之后跟随一个或多个 CONTIUNATION 帧。我们可以把 CONTINUATION 帧当做特殊的 HEADERS 帧。那么,为什么要使用特殊的帧,而不是再次使用 HEADERS 帧?如果重复使用 HEADERS,那么后续的 HEADERS 帧的负载就得经过特殊处理才能和之前的拼接起来。 |
||||||
|
> |
||||||
|
> 需要注意的是,由于 HEADERS 和 CONTINUATION 帧必须是有序的,使用 CONTINUATION 帧会破坏或减损多路复用的益处。CONTINUATION 帧是解决重要场景(大首部)的工具,但只能在必要时使用。 |
||||||
|
|
||||||
|
### 消息 |
||||||
|
|
@ -0,0 +1,55 @@ |
|||||||
|
--- |
||||||
|
Web 优化 “黑魔法” 的动机与方式 |
||||||
|
--- |
||||||
|
|
||||||
|
### 目录 |
||||||
|
|
||||||
|
1. 关键性能指标 |
||||||
|
2. HTTP/1 的问题 |
||||||
|
3. Web 性能优化最佳实践 |
||||||
|
|
||||||
|
### 关键性能指标 |
||||||
|
|
||||||
|
指标:延迟、带宽。 |
||||||
|
|
||||||
|
#### 延迟 |
||||||
|
|
||||||
|
延迟是指 IP 数据包从一个网络端点到另一个网络端点所花费的时间。与之相关的是往返时延(RTT),它是延迟的时间的两倍。延迟是制约 Web 性能的主要瓶颈,尤其对于 HTTP 这样的协议,因为其中包含大量往返于服务器的请求。 |
||||||
|
|
||||||
|
#### 带宽 |
||||||
|
|
||||||
|
只要带宽没有饱和,两个网络端点之间的连接会一次处理尽可能多的数据量。依据 Web 页面引用资源的大小和网络连接的传输能力,带宽可能会成为性能的瓶颈。 |
||||||
|
|
||||||
|
### HTTP/1 的问题 |
||||||
|
|
||||||
|
问题:队头阻塞、低效的 TCP 利用、臃肿的首部和受限的优先级设置。 |
||||||
|
|
||||||
|
#### 队头阻塞 |
||||||
|
|
||||||
|
浏览器很少只从一个域名获取一份资源。大多数时候,它希望能同时获取许多资源。设想这样一个网站,它把所有图片放在单个特定的域名下。HTTP/1 并未提供机制来同时请求这些资源。如果仅仅使用一个连接,它需要发起请求,等待响应,之后才能发起下一个请求。h1 有个特性叫管道化,允许一次发送一组请求,但是只能按照发送顺序依次接收响应。而且,管道化备受互操作性和部署的各种问题的困扰,基本没有实用价值。 |
||||||
|
|
||||||
|
在请求应答过程中,如果出现任何状况,剩下所有的工作都会被阻塞在那次请求应答之后。这就是 ”队头阻塞“,它会阻塞网络传输和 Web 页面渲染,直至失去响应。为了防止这种问题,现代浏览器会针对单个域名开启 6 个连接,通过各个连接分别发送请求。它实现了某种程度上的并行,但是每个连接仍会受到 ”队头阻塞“ 的影响。 |
||||||
|
|
||||||
|
#### 低效的 TCP 利用 |
||||||
|
|
||||||
|
传输控制协议(TCP)的设计思路是:对假设情况很保守,并能够公平对待同一网络的不同流量的应用。它的避免拥塞机制被设计成即使在最差的网络状况下仍能起作用,并且如果有需求冲突也保证相对公平。这是它取得成功的原因之一,它的成功并不是因为传输数据最快,而是因为它是最可靠的协议之一,涉及的核心概念就是拥塞窗口。拥塞窗口是指,在接收方确认数据包之前,发送方可以发出的 TCP 包的数量。例如,如果拥塞窗口指定为 1,那么发送方发出 1 个数据包之后,只有接收方确认了那么包,才能发送下一个。 |
||||||
|
|
||||||
|
一般来讲,每次发送一个数据包并不是非常低效。TCP 有个概念就慢启动,它用来探索当前连接对应拥塞窗口的合适大小。慢启动的设计目标是为了让新连接搞清楚当前网络状况,避免给已经拥堵的网络继续添乱。它允许发送者在收到每个确认回复后额外发送 1 个未确认包。这意味着新连接在收到 1 个确认回复之后,可以发送 2 个数据包;在收到 2 个确认回复后,可以发 4 个;以此类推,这种几何级数增长很快就会达到协议规定的发包数上限,这时候连接将进入拥塞避免阶段。 |
||||||
|
|
||||||
|
![TCP 拥塞控制.png](https://i.loli.net/2019/11/11/vWPindHfIFsyXB8.png) |
||||||
|
|
||||||
|
这种机制需要几次往返数据请求才能得知最佳拥塞窗口大小。但在解决性能问题时,就这区区几次数据往返也是非常宝贵的时间成本。现代操作系统一般会取 4~10 个数据包作为初始拥塞窗口大小。如果你把一个数据包设置为最大值下限 1460 字节(也就是最大有效负载),那么只能先发送 5840 字节(假定拥塞窗口为 4),然后就需要等待接受确认回复。如今的 Web 页面平均大小约 2MB,包括 HTML 和所有依赖的资源。在理想情况下,这需要大约九次往返请求来传输完整个页面。除此之外,浏览器一般会针对用一个域名开启 6 个并发连接,每个连接都免不了拥塞窗口调节。 |
||||||
|
|
||||||
|
前面提到过,因为 h1 并不支持多路复用,所以浏览器一般会针对指定域名开启 6 个并发连接。这意味着拥塞窗口波动也会并行发生 6 次。TCP 协议保证那些连接都能正常工作,但是不能保证它们的性能是最优的。 |
||||||
|
|
||||||
|
#### 臃肿的消息首部 |
||||||
|
|
||||||
|
虽然 h1 提供了压缩被请求内容的机制,但是消息首部却无法压缩。消息首部可不能忽略,尽管它比响应资源小很多,但它可能占据请求的绝大部分,如果算上 Cookie,有几千字节就很正常了。 |
||||||
|
|
||||||
|
#### 受限的优先级设置 |
||||||
|
|
||||||
|
如果浏览器针对指定域名开启了多个 Socket,开始请求资源,这时候浏览器能指定优先级的方式是有限的:要么发起请求,要么不发起。然后 Web 页面上某些资源会比另一些更重要,这必然会加重资源的排队效应。这是因为浏览器为了先请求优先级高的资源,会推迟请求其他资源。但是优先级高的资源获取之后,在处理的过程中,浏览器并不会发起新的资源请求,所以浏览器无法利用这段时间发送优先级低的资源,总的页面下载时间因此延长了。 |
||||||
|
|
||||||
|
### Web 性能优化最佳实践 |
||||||
|
|
||||||
|
参见《Web 性能优化最佳实践》。 |
After Width: | Height: | Size: 165 KiB |
After Width: | Height: | Size: 209 KiB |
Loading…
Reference in new issue