是什么
BigPipe 是 Facebook 在 2010 年中一篇 博文 专门介绍的可以提高页面加载速度的网页架构。通过在基础层面重新设计服务器如何交付动态网页,大幅度提高了网页的加载速度。并且,这个结果,是在不改变现有 Web 技术架构的情况下达成的。
大体上,是通过将网页分割为被称为 pagelet 的小块,然后逐一通过由服务器和浏览器产生的操作组成的流水线,当一个 pagelet 经历完流水线后可以立即交付给用户,即在前端展示出来,而不会被其他 pagelet 的加载阻塞掉。
动机
那么 Facebook 这么做的目的是什么呢?为什么要引入 BigPipe 架构呢?
让我们先来想想,现在的网页和以前的网页区别在哪呢?答案是,随着用户需求,现在的网页变得越来越 动态化 了,但是服务器交付网页的速度却越来越慢了。换句话说就是 网页日益增长的动态化需求同落后的网页交付模式 之间的矛盾。
传统的网页交付模型是这样的:
- 浏览器发送 HTTP 请求到服务器
- 服务器解析请求从其他接口或数据库拉取数据,然后生产出网页附在响应上返回给浏览器
- 响应经过网络传输到浏览器
- 浏览器解析响应,构建 DOM 树,并下载 CSS 和 JS 文件
- 下载完成 CSS 后立刻开始解析,解析后结合 DOM 树生成渲染树。
- 下载完成 JS 后立刻开始解析执行,并阻塞 DOM 渲染。
传统网页交付模型最大问题在于,非常多的步骤是被相互阻塞的,你不可能浏览器拉取数据的时候生成 HTML,或者在生成 HTML 的时候发送响应给浏览器。当然,我们有一些优化手段,但这些优化手段的问题在于,无论怎么优化,我们总是局限于一端。
我们可以在前端提前下载 JS,延迟执行来保证 JS 不会阻塞 DOM 渲染,或者在后端缓存接口数据,更快的生成 HTML,但是我们没法做到在服务器在产生 HTML 时,浏览器就开始解析 HTML。反过来也一样,当浏览器收到响应后,服务器就什么帮助也做不了了。
通过 BigPipe,我们就可以做到传统优化手段不能触及的地方,通过去并行的进行服务器和浏览器的某些行为,不仅可以减少前后端传输的延迟,更能使我们的首屏时间大大加快。
BigPipe 架构最适用于富应用,即应用的数据来源于多个接口。在传统的架构下,服务器必须等待所有数据请求都返回后才能生成 HTML,也就是任一个请求都会阻塞掉整个交付流程。
原理
为了使得浏览器和服务器可以并行的执行某些操作,BigPipe 首先需要我们将网页分割成被称为 pagelet 的小块。然后我们将网页交付流程分解为如下阶段:
- 解析请求:服务器校验和解析 HTTP 请求
- 数据拉取:服务器从接口或数据库获得数据
- HTML 标记生成:生成对应的 HTML 片段
- 网络传输:服务器返回响应到浏览器
- CSS 下载:浏览器下载需要的 CSS 文件
- 构建 DOM 树和应用样式:浏览器构建 DOM 树,并将相应的 CSS 样式规则应用到节点上
- JavaScript 下载:下载网页引用的 JavaScript 文件
- JavaScript 执行:浏览器执行下载后的 JavaScript
1-3 阶段都是在服务器上执行的,5-8 则是在浏览器上执行的。每一个 pagelet 必须依次经过上述所有阶段,但是 BigPipe 使得多个 pagelte 可以在 同一时刻 分别进行不同的步骤。举个例子,当某个 pagelet 正在 3 阶段,与此同时,可能有个 pagelet 在 2 阶段,甚至有个 pagelet 已经在 8 阶段了。
这里用 Facebook 的例图来举个例子。在 BigPipe 架构下,一个 HTTP 请求的生命周期是这样的:首先浏览器发送请求到服务器。服务器在收到请求后立刻返回一份不完整的 HTML 片段,然后将请求挂起,并不结束
<html>
<head>
<script>
window.BigPipe = {};
// ... do something
BigPipe.handlePagelet = (pagelet) => {
const conainer = document.querySelector(pagelet.selector)
downloadAllCSS(pagelet.css)
.then(() => {
conainer.innerHTML = pagelet.content;
// ... do things
});
}
<script>
</head>
<body>
<div id="profile"></div>