引言
面试时,被问 Http 2.0 有哪些内容的时候,你:
我们的网站将 Http 1.1 升级到 Http 2.0,网站加载性能相对 Http 1.1 来说确实有比较大的提升🚀
当我们聊起 http 2.0, 总能聊到这几个特性:
- 二进制分帧传输
- 多路复用
- 头部压缩
- 请求优先级
- 服务器推送
我们在分析项目页面加载性能时,发现有一个场景下 Http 2.0 会比 Http 1.1 表现得更慢。
下面我们带着问题出发,再根据 Http 2.0 特性,分析究竟为何?
问题是什么
我们发现一个页面加载的内容不多,但是加载发现时快时慢。如下图:
详情会等待列表加载完毕后加载,有时候列表快、详情出来慢一些,有时候则相反,列表加载慢、详情出来快一些。
问题分析
开始我们怀疑影响页面加载慢因素有:
- 服务器资源不够
- 慢查询 SQL 语句
- 某个缓存数据失效
- 网络不稳定
我们调取服务器硬件监控发现资源都是健康,也未见一些波动,所以我们第 1 点 资源问题直接排除。
然后我们在页面多刷几下后,去翻了 MySql 的慢日志文件,也没有相关日志,所以第 2 点 慢查询也排除。
我们再来看看浏览器的 Network 面板,发现请求的细节是这样:
上图我们发现有很多慢接口, 它们加载时长我们通过图可以清晰看到,同时提供了关键信息。
我们来看了一个 /view 接口的加载表现,传输数据量大小 3.2m ,传输时间为 3.2 秒。
其中时间包括两部分,一部分是服务器处理时间(Waiting for sever resopnes 绿色部分),另一部分是浏览器下载时间(Content download 蓝色部分)。/view 响应时间和下载时间都比较长
我们对 /view 与其他接口进行分析,发现没有做缓存相关的功能。所以排除第 3 点缓存失效因素。
我们再将其他接口一个个单独发请求,会很快。
但是进入这个页面,和 /view 接口一起的时候就表现得很慢,所以也排除了 第 4 点网络原因。
多试几次,发现会导致其他接口变慢罪魁祸首就是 /view 接口。
刚开始以为是 /view 接口导致服务器资源下降,进一步会影响其他接口处理,但是上面已经做过服务器资源分析,这个想法也被否决了。
回过头来,还有一个细节我们忽略了
每次列表、详情这些接口,几乎都会和 /view 接口在同一时间点结束
我们前面也测试了其余接口速度很快,所以猜测肯定存在某种机制,其余接口在等 /view 这个接口加载完毕。(ps:前端并没有把其余接口和 /view 同步阻塞发送,几乎是异步同时发送。)
我们再打开浏览器 Performance 面板进一步分析,现象如下:
很多接口都在
/view 接口时间范围内,且部分接口与 /view 同时结束
这个现象是比较奇怪的,我们将浏览器的 Http 2.0 禁用,用 Http 1.1 打开再来看发现:
在 Http 1.1 下,虽然 /view 接口还是慢,但是其他接口很快就响应结束,页面列表和详情居然很快就加载回来了!
说到这里,结合我们文章的主题和 Http 2.0 特性,你能猜到可能是什么影响的么?
Http 2.0 多路复用
没错,问题原因正是本章节的标题。
但是在聊 多路复用 前,我们也看看 二进制分帧传输
二进制分帧(Binary Format)- http2.0的基石
Http2.0 之所以能够突破 http1.X 标准的性能限制,改进传输性能,实现低延迟和高吞吐量,就是因为其新增了二进制分帧层。
帧(frame):包含:类型Type, 长度Length, 标记Flags, 流标识Stream和frame payload有效载荷。
消息(message): 一个完整的请求或者响应,比如请求、响应等,由一个或多个 Frame 组成。
流(stream): 是连接中的一个虚拟信道,可以承载双向消息传输。每个流有唯一整数标识符。为了防止两端流ID冲突,客户端发起的流具有奇数ID,服务器端发起的流具有偶数ID。
流标识是描述二进制frame的格式,使得每个frame能够基于Http 2.0发送,与流标识联系的是一个流,每个流是一个逻辑联系,一个独立的双向的 frame 存在于客户端和服务器端之间的 Http2.0 连接中。一个 Http2.0 连接上可包含多个并发打开的流,这个并发流的数量能够由客户端设置。
在二进制分帧层上,Http 2.0 会将所有传输信息分割为更小的消息和帧,并对它们采用二进制格式的编码将其封装,新增的二进制分帧层同时也能够保证 Http 的各种动词、方法、首部都不受影响,兼容上一代标准。
其中,http1.X 中的首部信息header封装到 Headers 帧中,而 request body 将被封装到 Data 帧中。
多路复用 (Multiplexing)
在http1.1中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量的限制,超过限制数目的请求会被阻塞。这也是为何一些站点会有多个静态资源 CDN 域名的原因之一。
而http2.0多路复用允许通过单一的连接,发起多重的请求-响应消息。有了上面的分帧机制后,http/2 不再依赖多个TCP连接去实现多流并行了。每个数据流都拆分成很多互不依赖的帧,而这些帧可以交错(乱序发送),还可以分优先级,多路复用意味着来自很多流的数据包能够混合在一起通过同样连接传输。当到达终点时,最后再在另一端把它们重新组合起来。
上图展示了一个连接上的多个传输数据流:客户端向服务端传输数据帧 stream5,同时服务端向客户端乱序发送stream1和stream3。这次连接上有三个响应请求乱序并行交换。
再举个例子:
上述是简述了二进制分帧、多路复用带来的优点和特性。
回到我们的问题现象,正因为数据被拆分成一帧一帧进行无序传输,快接口的数据帧和慢接口的数据帧,会在流上同时传输。
这可能会导致 快接口 在 “等” 慢接口 /view, 如下图:
一句话总结就是:
在 http 2.0 多路复用下,本来加载快的资源,可能存在等待加载慢的资源的现象。
那有的小伙伴就会问,这个问题看起来也挺严重的,是不是说明不要乱升级 Http 2.0 ?
答案肯定是否定的,在绝大数场景里,Http 2.0 比 Http 1.1 快得不止一点,尤其是你的网站要加载的资源数特别多的时候,比如新闻网、商城等等,多路复用的优势就很明显,你的项目也可以把浏览器禁用 Http 2.0 与 Http 1.1 进行对比看看。
最终,我们那个问题通过优化 /view 慢接口本身传输数据量和传输速度,问题得以解决,页面加载也不会时快时慢了。
总结
此文旨在通过分享一个性能问题,包括分析问题思路,让我们更深入了解 Http 2.0 一些特性。
为大家后续遇到类似的问题提供宝贵意见,如以上文章写的地方欠妥,欢迎指出交流~