前文我们知道了gRPC如何使用protobuf来组织数据,达到高效编码,高压缩率的目标。现在还有很重要的一个点就是,我们需要知道这些数据是如何在网络中被传输的,达到低资源消耗低高效传输目标,内容将围绕几个点展开:
- HTTP2要解决的问题,HTTP1.1的缺点
- HTTP2的原理,它是如何降低传输成本,借此我们更深入理解何为二进制编码。同时它是如何提高网络资源利用效率,同时重温多路复用的思想
网络传输的目标
数据的传输,都是被切割成一个个小块,包在层层网络协议里面。通过一个个路由器依次转发,终达到目的地,被重新组装起来。这是网络传输的基本原理,在这个过程中,有两个更古不变的的目标:
- 更快的传输,快的背后就是少,传输的数据越少,越小,整体的速度也就越快
- 更低的资源消耗。这背后是资源的高效利用,就像cpu那样,压榨的越厉害,就越节约资源
http1.1 被视为差生
- 冗余文本过多,导致传输体积很大。作为一款经典的无状态协议,它使得web后端可以灵活低转发,横向扩展。但是其代价是每个请求会带上重复的header, 这些文本内容会消耗很多空间和更快传输的目标相悖
- 并发能力差,网络资源利用率低。http是基于文本的协议,请求的内容打包子啊header/body中,内容通过\r\n来分割。同一个TCP连接中,无法区分request/response是属于哪个请求。所以无法通过一个TCP连接并发地发送多个请求,只能等上一个请求的response回来,才能发送下一个请求,否则无法区分不同的连接
- 很多浏览器为了兼顾下载速度和资源消耗,会对同一个域名限制TCP连接数量,如chrome是6个左右。剩下的则需要排队。
http2当救世主
http1.1在速度和成本上的权衡让人纠结不已,http2的出现就是为了优化这些问题。在更快的传输和更低成本两个目标上更进了一步。有以下几个基本点:
- http2未改变http的语义,如get/post等,只是在传输上做了优化
- 引入帧,流的概念,在TCP连接中,可以区分多个request/response
- 一个域名只有一个TCP连接,借助帧,流可以实现多路复用,降低资源消耗
- 引进二进制编码,降低header带来的空间占用
核心可以分为头部压缩和多路复用,这两个点都服务于更快于更快的传输,更快的资源消耗这两个目标,与上文呼应。
头部压缩
现在的web页面,大多比较复杂,都打开一个地址。动辄产生几十个请求,这会发送大量的header,大部分内容都是一样的内容,以baidu为例。
request:
GET HTTP/1.1
Host: www.baidu.com
Cache-Control: no-cache
Postman-Token: a9702bac-94c4-c7da-2041-7c7ac5f85b6e
response:
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=UTF-8
Server: Apache
Transfer-Encoding: chunked
Vary: Accept-Encoding
这些文本内容一次次重复地发送,占用了大量的带宽,如何将这些成本降下去,而又保留http无状态的优点呢?
基于这个想法,HPACK[2],全称为HTTP2头部压缩。它以极富创造力的方式,提供了两个方式来降低eader的传输占用。
- 将高频使用的header编成一个静态表,每个header对应一个数组索引。每次只用传这个索引,而不是冗长的文本。
- 支持动态地在表中增加header
多路复用
http1.1的核心尴尬在于,在同一个tcp连接中,没有办法区分response是属于哪个请求。一旦多个请求返回的文本内容混在一起,就天下大乱了。
http2提出了流的概念,每一次请求对应一个流,有一个唯一的ID, 用来区分不同的请求。基于流的概念,进一步提出了帧。一个请求的数据会被分为多个帧,方便进行数据分割传输。每个帧都唯一属于某一个流ID,将帧按照流ID进行分组,即可分离出不同的请求。这样同一个TCP连接中就可以同时并发多个请求,不同的请求的帧数据可以穿插在一起。根据流的分组即可。这样直接解决了http1.1的核心痛点,通过这种复用TCP连接的方式,不用再同时建多个连接,提升了TCP的利用效率。这也是多路复用思想的一种落地方式。在很多消息队列协议中也广泛存在,比如AMQP, 其channel的概念和流如出一辙,大道相通。