bigPipe 原理分析
一、什么是bigPipe?
bigPipe是由facebook提出来的一种动态网页加载技术。它将网页分解成称为pagelets的小块,然后分块传输到浏览器端,进行渲染。它可以有效地提升首屏渲染时间。
为了说清楚什么是bigPipe,我先需要介绍下目前的常规渲染方式,以及可以进行优化的方向。
二、网页首屏加载方案
注:首屏加载方案指的是在服务端就已经吐出页面的方案,也就是说有SSR的方案,那些纯客户端渲染的方案不做比较,因为它们没有首屏要求。
现有的网页首屏加载方案一般会经过以下阶段:
发送http请求到服务端
服务端接收并分析请求
服务端根据请求从存储层获取相关数据,这里可能会比较耗时,比如如果首页涉及多个模块(广告位、推荐、内容列表、用户信息、好友列表等)
服务端准备好所有内容,拼接成完整的html文档
发送回客户端
客户端接收完整的html文档
构建dom、cssom, 生成render tree,渲染出指定页面
如下图所示:
以上加载方案的缺点是,当2、3、4、5 步在服务端进行的时候,浏览器只能是傻傻地等待,做不了任何事情!
而且第3步并行拉取业务数据在某些场景下(模块多,业务场景复杂),是会占用比较多的时间的。而且只要其中某个模块的数据如果拉取较慢,会拖慢整个首屏的显示。
更糟糕的情况下,某些模块数据相互依赖,导致需要串行拉取数据,那造成的浏览器的等待则会更久。
三、优化方案
以上方案在一些服务端渲染页面中相当常见,不足之处也很明显。
而bigPipe就是针对第3、4步进行优化,让服务端在准备好某个模块的数据后,立马返回给客户端显示,而不必要等待完整的数据和html生成,再发送给客户端。
客户端在接收到某一部分内容后,就可以开始渲染,显示执行(这里可以动态请求需要的css,js 等等)。
如下图,客户端拉取业务数据和客户端渲染页面可以并行。如果某一部分比如广告信息拉取超时,也并不影响其他部分率先渲染显示。
这样,一个完整的页面就可以拆成多个部分,分块渲染,而无需等到拿到完整的页面返回,再渲染。要知道, 如果要等到完整页面返回,在这之前,浏览器只能是一片空白!
四、关键技术和原理
想要实现以上优化方案,可以利用现成的技术,所以有比较好的兼容性。
1.分段传输
bigPipe依赖于分段传输html页面,所以这是实现bigPipe的一个基础。
http1.1
如果在http1.1版本上实现,那需要设置Transfer-Encoding为chunked,也就是分块传输编码。
关于分块传输编码:
分块传输编码允许服务器为动态生成的内容维护HTTP持久连接。在这种情况下,不能使用HTTP Content-Length头来分隔内容和下一个HTTP请求/响应,因为内容大小未知。
分块编码的好处是,在返回客户端前不必生成完整的内容,因为它允许将内容作为分块进行流式处理,并明确地发出内容结尾的信号,从而使连接可用于下一个HTTP请求/响应。
在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。
每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。
最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。
http 2
如果你使用的是http2,那则无需设置Transfer-Encoding为chunked,因为http2本身就是支持这种分块传输的协议。
2.浏览器渲染原理
说到浏览器渲染,我们可以简单地把它归为五个阶段。(为了方便分析渲染过程,先不考虑有js的情况)
阶段一:
解析html文档,生成节点,构建dom树
阶段二:
在阶段一中,如果遇到css(内嵌在html文档或者外链或者内联样式都一样),则会解析css文档,生成cssom。
阶段三:
阶段一和阶段二都是可以并行的,等到dom和cssom准备好,会进行合并,生成render tree。
阶段四:
根据render tree进行layout。
阶段五:
绘制到显示区域。
整个阶段如下图所示
幸运的是,浏览器并不会等解析完完整的html文档后,才进行layout 和paint。
浏览器已经对显示html文档进行了优化,会尽快将解析好的部分呈现给用户。也就是,上图所谓的一次渲染过程,在分块传输的时候,是可以多次进行的。直到接收到</body></html>闭合标签。
五、实践
bigPipe技术的基本原理在上面就已经介绍完了。实践都是基于以上原理而来的。
常规实践是将页面分成各个模块,称之为一个个pagelets,每个pagelets包含自己需要的模板数据,css样式和需要的js。
在传输pagelets之前,先将页面主体layout传输到客户端,先进行渲染,此时,用户已经可以看到页面的主体框架了。
之后,再将服务端处理完的pagelets一个个返回,在客户端渲染。如下图: