GO HTTP1.1 与 HTTP2.0 的使用和简单分析

Python013

GO HTTP1.1 与 HTTP2.0 的使用和简单分析,第1张

测试结果

我们设定服务端处理一次请求需要 10 ms,我们可以得到三个信息

所以我们可以得到结论,对于一个稳定的服务器,HTTP1.1 单位时间的处理效率和连接数成正比,需要更高的处理效率就必须不断的增加 TCP 连接。因为 HTTP1.1 的请求遵循 FIFO。

测试结果

GO HTTP2.0 我没有找到设置连接池数量的地方,但是在测试中执行

发现连接数量一直是 4 个,同时我们可以看到,仅仅 4 个 TCP 连接,就能在并发达到 200 的时候 1s 内执行 2w 次请求。效率远超 HTTP1.1,且不需要更多的 TCP 连接。

我们现在用到的HTTP协议,基本上分为HTTP1.1和HTTP2.0。我们这里说的HTTP2.0新特性是相对于HTTP1.X而言的,总体来说HTTP2对1.X协议语意完全兼容,而且在性能上大幅提升了。这里介绍他的几个新特性:

HTTP2.0性能增强的核心:二进制分帧。

HTTP 2.0最大的特点: 不会改动HTTP 的语义,HTTP 方法、状态码、URI 及首部字段,等等这些核心概念上一如往常,却能致力于突破上一代标准的性能限制,改进传输性能,实现低延迟和高吞吐量。而之所以叫2.0,是在于新增的二进制分帧层。

在二进制分帧层上,HTTP 2.0 会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码,其中HTTP1.x的首部信息会被封装到Headers帧,而我们的request body则封装到Data帧里面。帧是数据传输的最小单位,以二进制传输代替原本的明文传输。

HTTP 2.0 所有的通信都在一个连接(TCP连接)上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。

HTTP性能的关键在于低延迟而不是高带宽!大多数HTTP 连接的时间都很短,而且是突发性的,但TCP 只在长时间连接传输大块数据时效率才最高。HTTP 2.0 通过让所有数据流共用同一个连接,可以更有效地使用TCP 连接,让高带宽也能真正的服务于HTTP的性能提升。

单连接多资源方式的好处:

1.可以减少服务连接压力,内存占用少了,连接吞吐量大了。

2.由于TCP连接减少而使网络拥塞状况得以改观。

3.慢启动时间减少,拥塞和丢包恢复速度更快。

HTTP/2 的四个概念:

Connection :1 个 TCP 连接,包含 1 个或者多个 stream。所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流。

Stream:一个双向通信的数据流,包含 1 条或者多条 Message。每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息。流是连接中的一个虚拟信道,可以承载双向消息传输。每个流有唯一整数标识符。为了防止两端流ID冲突,客户端发起的流具有奇数ID,服务器端发起的流具有偶数ID。

Message:消息是指逻辑上的HTTP消息(请求/响应)。一系列数据帧组成了一个完整的消息。比如一系列DATA帧和一个HEADERS帧组成了请求消息。

Frame:最小通信单位,以二进制压缩格式存放内容。来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。

Frame 由 Frame Header 和 Frame Payload 两部分组成。所有帧都以固定的 9字节大小的头作为帧开始,后跟可变长度的有效载荷payload。

HEADERS:用户传输关于流的额外的首部字段

PRIORITY:用户指定或者重新指定引用资源的优先级

RST_STRING:用于通知流的非正常终止

SETTINGS:用于通知两端通信方式的数据配置

PUSH_PROMISE:用于发出创建流和服务器引用资源的要约

PING:用于计算往返时间,执行“活性”检查

GOAWAY:用于通知对端停止在当前连接的创建流

WINDOW_UPDATE:用于针对个别流或个别连接实现流量控制

CONTINUATION:用于继续一系列首部块片段

Flags:

标志位,常用的标志位有 END_HEADERS 表示头数据结束,相当于 HTTP/1里头后的空行(“\r\n”)。

R:

保留的 1 位。该位的语义未定义,发送时必须保持未设置 (0x0),接收时必须忽略。

Stream Identifier:

流标识符,表示为无符号 31 位整数。由客户端发起的流必须使用奇数编号的流标识符;那些由服务器发起的必须使用偶数编号的流标识符。DATA 帧必须与某一个流相互关联。

stream ID 的作用:

1.实现多路复用的关键。接收端的实现可以根据这个 ID 并发、组装消息。同一个 stream 内 frame 必须是有序的。

为什么要压缩?

在 HTTP/1 中,HTTP 请求和响应都是由「状态行、请求 / 响应头部、消息主体」三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件(例如图片、音频),但状态行和头部却没有经过任何压缩,直接以纯文本传输。

根据 HTTP Archive 的统计,当前平均每个页面都会产生上百个请求。越来越多的请求导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。

以下是我随手打开的一个页面的抓包结果。可以看到,传输头部的网络开销超过100kb,比HTML 还多:

HTTP/2协议中定义了 HPACK,这是一种新的压缩方法,它消除了多余的header 字段,将漏洞限制到已知的安全攻击,并且在受限的环境中具有有限的内存需求。HPACK 格式特意被设计成简单且不灵活的形式:两种特性都降低了由于实现错误而引起的互操作性或安全性问题的风险;没有定义扩展机制,只能通过定义完整的替换来更改格式。

需要注意的是,http 2.0关注的是首部压缩,而我们常用的gzip等是报文内容(body)的压缩,二者不仅不冲突,且能够一起达到更好的压缩效果。

(1)如何进行头部压缩

简单说,HPACK头部压缩需要在支持 HTTP/2 的浏览器和服务端之间:

1)维护一份相同的静态表(Static Table),包含常见的头部名称,以及常见的头部名称与值的组合(静态表内容共61项,索引号1-61);

2)维护一份相同的动态表(Dynamic Table),当一个header name 或者header value在静态表中不存在,会被插入动态表中,可以动态地添加内容(动态表索引从62开始);

客户端和服务端会共同维护一份动态表

第一次发送的时候需要明文发送(要经过Huffman编码),第二次及第N次发送索引号

3)对不存在的头部使用哈夫曼编码(Huffman Coding),并动态缓存到索引(动态表)

+-------+-----------------------------+---------------+

| 1 | :authority | |

| 2 | :method | GET |

| 3 | :method | POST |

| 4 | :path | / |

| 5 | :path | /index.html |

| 6 | :scheme | http |

| 7 | :scheme | https |

| 8 | :status | 200 |

| 9 | :status | 204 |

| 10 | :status | 206 |

| 11 | :status | 304 |

| 12 | :status | 400 |

| 13 | :status | 404 |

| 14 | :status | 500 |

| 15 | accept-charset | |

| 16 | accept-encoding | gzip, deflate |

| 17 | accept-language | |

| 18 | accept-ranges | |

| 19 | accept | |

| 20 | access-control-allow-origin | |

| 21 | age | |

| 22 | allow | |

| 23 | authorization | |

| 24 | cache-control | |

| 25 | content-disposition | |

... ...

| 60 | via | |

| 61 | www-authenticate | |

+-------+-----------------------------+---------------+

http1.1中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量的限制,超过限制数目的请求会被阻塞。这也是为何一些站点会有多个静态资源CDN 域名的原因之一。

http 2.0 连接都是持久化的,而且客户端与服务器之间也只需要一个连接(每个域名一个连接)即可。http2连接可以承载数十或数百个流的复用,多路复用意味着来自很多流的数据包能够混合在一起通过同样连接传输。当到达终点时,再根据不同帧首部的流标识符重新连接将不同的数据流进行组装。

2)stream流可以单方面建立和使用,也可以由客户端或服务器共享。

3)任何一个端都可以关闭 stream 流。

4)在stream流上发送帧的顺序非常重要。收件人按照收到的顺序处理帧。特别是,HEADERS 和 DATA 帧的顺序在语义上是重要的。

5)stream流由整数标识。stream 流标识符是由发起流的端点分配给 stream流的。

服务器可以对一个客户端请求发送多个响应,服务器向客户端推送资源无需客户端明确地请求。并且,服务端推送能把客户端所需要的资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤。

正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。Server Push 让 http1.x 时代使用内嵌资源的优化手段变得没有意义;如果一个请求是由你的主页发起的,服务器很可能会响应主页内容、logo 以及样式表,因为它知道客户端会用到这些东西,这相当于在一个HTML 文档内集合了所有的资源。

不过与之相比,服务器推送还有一个很大的优势:可以缓存!也让在遵循同源的情况下,不同页面之间可以共享缓存资源成为可能。

注意两点:

1、推送遵循同源策略;

2、这种服务端的推送是基于客户端的请求响应来确定的。

当服务端需要主动推送某个资源时,便会发送一个 Frame Type 为PUSH_PROMISE 的 Frame,里面带了 PUSH 需要新建的 Stream ID。意思是告诉客户端:接下来我要用这个 ID 向你发送东西,客户端准备好接着。客户端解析 Frame 时,发现它是一个 PUSH_PROMISE 类型,便会准备接收服务端要推送的流。

启用http2.0后会给性能带来很大的提升,但同时也会带来新的性能瓶颈。因为现在所有的压力集中在底层一个TCP连接之上,TCP很可能就是下一个性能瓶颈,比如单个TCP packet丢失导致整个连接阻塞,无法逃避,此时所有消息都会受到影响。

注:QUIC协议代替TCP协议中关于可靠、流量控制的部分

窃听风险:通信使用明文,明文报文不具备保密性,内容可能被窃听

冒充风险:不验证通信方的身份(不进行身份验证),有可能遇到伪装

篡改风险:无法证明报文的完整性,有可能已遭篡改