HTTP1.1的协议里,在一个连接里传送数据都是串行顺序传送的,必须等上一个请求全部处理完后,下一个请求才能进行处理,导致这些请求期间这条连接并不是满带宽传输的,即使是HTTP1.1的pipelining可以同时发送多个request,但response仍是按请求的顺序串行返回,只要其中一个请求的response稍微大一点或发生错误,就会阻塞住后面的请求。
HTTP2 这里的多路复用协议解决了这些问题,它把在连接里传输的数据都封装成一个个stream,每个stream都有标识,stream的发送和接收可以是乱序的,不依赖顺序,也就不会有阻塞的问题,接收端可以根据stream的标识去区分属于哪个请求,再进行数据拼接,得到最终数据。
解释下多路复用这个词,多路可以认为是多个连接,多个操作,复用就是字面上的意思,复用一条连接或一个线程。HTTP2这里是连接的多路复用,网络相关的还有一个I/O的多路复用(select/epoll),指通过事件驱动的方式让多个网络请求返回的数据在同一条线程里完成读写。
客户端来说,iOS9 以上 NSURLSession 原生支持 HTTP2,只要服务端也支持就可以直接使用,Android 的 okhttp3 以上也支持了 HTTP2,国内一些大型 APP 会自建网络层,支持 HTTP2 的多路复用,避免系统的限制以及根据自身业务需要增加一些特性,例如微信的开源网络库 mars,做到一条长连接处理微信上的大部分请求,多路复用的特性上基本跟
HTTP2 一致。
TCP队头阻塞
HTTP2 的多路复用看起来是完美的解决方案,但还有个问题,就是队头阻塞,这是受限于 TCP 协议,TCP 协议为了保证数据的可靠性,若传输过程中一个 TCP 包丢失,会等待这个包重传后,才会处理后续的包。HTTP2的多路复用让所有请求都在同一条连接进行,中间有一个包丢失,就会阻塞等待重传,所有请求也就被阻塞了。
对于这个问题不改变 TCP 协议就无法优化,但 TCP 协议依赖操作系统实现以及部分硬件的定制,改进缓慢,于是GOOGLE 提出 QUIC 协议,相当于在 UDP 协议之上再定义一套可靠传输协议,解决 TCP 的一些缺陷,包括队头阻塞。QUIC协议虽然是基于UDP,但它不但具有TCP的可靠性、拥塞控制、流量控制等,QUIC 协议相对于 HTTP2 最大的优势是对TCP队头阻塞的解决,另外,QUIC协议具有TLS的安全传输特性,实现了TLS的保密功能,同时又使用更少的RTT建立安全的会话。
3 数据
第三个问题,传输数据大小的问题。数据对请求速度的影响分两方面,一是压缩率,二是解压序列化反序列化的速度。目前最流行的两种数据格式是 json 和 protobuf,json 是字符串,protobuf 是二进制,即使用各种压缩算法压缩后,protobuf 仍会比 json 小,数据量上 protobuf 有优势,序列化速度 protobuf 也有一些优势,这两者的对比就不细说了。可以看此文章protobuf
在iOS上的实践来进一步了解protobuf
压缩算法多种多样,也在不断演进,最新出的 Brotli 和Z-standard实现了更高的压缩率,Z-standard 可以根据业务数据样本训练出适合的字典,进一步提高压缩率,目前压缩率表现最好的算法。
除了传输的 body 数据,每个请求 HTTP 协议头的数据也是不可忽视,HTTP2 里对 HTTP 协议头也进行了压缩,HTTP 头大多是重复数据,固定的字段如 method 可以用静态字典,不固定但多个请求重复的字段例如 cookie 用动态字典,可以达到非常高的压缩率,这里有详细介绍。
通过 HTTPDNS,连接多路复用,更好的数据压缩算法,可以把网络请求的速度优化到较不错的程度了,接下来再看看弱网和安全上可以做的事情。
二 弱网
手机无线网络环境不稳定,针对弱网的优化,微信有较多实践和分享,包括:
- 1、 提升连接成功率复合连接,建立连接时,阶梯式并发连接,其中一条连通后其他连接都 关闭。这个方案结合串行和并发的优势,提高弱网下的连接成功率,同时又不会增加服务器资源消耗: