Facebook让网站速度提升一倍的BigPipe技术分析
近来Facebook高调宣布将网站性能提高一倍,平均访问时间从5s降至2.5s,公布了名为BigPipe的优化技术的一些概况.性能提升一倍这个结果无疑是极其诱人的,如果各个网站都能应用这种技术拿到这样的结果,那BigPipe很有可能会成为匹敌Ajax的技术革新.它究竟是怎样的?会给你的应用带来这样的提升么?
先来摘要:
BigPipe原理:再简单不过了,这就像在餐馆吃饭,先选好桌子点好菜(确定用户布局和要展现的模块),单子下到厨房后,多个大厨就可以同时上阵(服务端并发),做好一样端上来一样吃一样(客户端并发).
BigPipe不具备普适性:网站性能优化再发展下去也不会增加一条类似"尽量减少http数","将CSS放在页面上部"这样不加任何说明和限制的优化准则---"使用Bigpipe提高网站速度",也就是说它不可能为所有应用都带来提升,或者说bigpipe可能给你的应用带来提升,但不会像Facebook那么大.
BigPipe适用的场合:首先,网页第一个请求时间较长( >500ms? ),在整个网页展现过程中不再是前端性能优化常说的可以忽略的10-20%.其次页面上的动态内容可以划分在多个区块内显示,且各个区块之间的关系不大,因为只是客户端和服务端并发是不够的,服务端各个数据中心也要能够并发才能最好的发挥bigpipe,各个区块的动态数据在服务端也能够通过url或cookie中的key并发获得(在facebook,整个key为userid).除了SNS外,大概搜索结果页可能可以用上,比如淘宝搜索结果页的主搜索,类目导航,相关推荐和广告可以使用BigPipe技术,不过回过头看第一个限制,如果搜索页本身很快,那带来的改进不会如Facebook明显.
BigPipe的启示:通过上面的这些限制看到Bigpipe是Facebook量身定做的优化方案,带来的一点提示是大家可以根据自己的应用为自己的应用定制优化方案,flush,ajax,静态资源combo,动态数据combo都是性能优化的手段,如何选择要看实际情况.
详细分析:
之前的一篇文章"在Yslow 34 Rules之后 -- 网站性能优化思路和进展"中提到网站性能优化进入精耕细作阶段,尽可能的满足各种Rules可以保证你能获得一个性能不错网站,而更进一步的优化则需要理清优化思路,结合具体应用的实际下功夫.Facebook的BigPipe技术就是这样发展出来的一种高度契合SNS类应用的优化技术.可以这么说,网站性能优化再发展下去也不会增加一条类似"尽量减少http数","将CSS放在页面上部"这样不加任何说明和限制的优化准则---"使用Bigpipe提高网站速度",也就是说它不可能为所有应用都带来提升.我们通过分析这种技术的产生过程,分析SNS类网站页面的特点,看看和你的应用有没有匹配的地方,来确定你的应用是否应该选用这种技术.另外也可以学习Facebook性能优化的套路,仔细考虑下自己应用的特点,是不是也能爆出让人眼前一亮的方案.
下面就开始分析Facebook网页的特点,网站性能优化的任何资料都经常见到"网页展现过程百分之八十至九十的时间消耗在了前端"这样的话,这种断言主要来自这类最常见的HTTP瀑布图,如下:
在网页的生命周期中,到达动态服务器的数据请求往往只是第一个HTML文本请求.相对于这个HTML到达浏览器之后再引入的一系列脚本,图片等等请求以及后续的渲染过程,第一个请求所占时间相当少.所以一般而言,再怎么做服务端的优化,对于整个展现过程的加快的帮助微小.但Facebook不同,下面是我的Facebook的个人主页的首个HTTP请求时间:
长达3.173s,刨除网络响应因素,仅接收数据阶段,时间也达到了1.65s.Facebook有慢的理由,因为SNS网站的页面是高度定制化的,每个区块的数据都需要大量的计算得来.所以对于Facebook来说,要提升整体页面响应时间,服务端的时间不得不考虑进去.如何优化?YSlow优化规则中"提早Flush"给了很好的提示."提早Flush"规则建议在</head><body>之间调用flush(),让这部分内容先输出给浏览器端,这样浏览器端可以在服务端还在生成主体HTML的同时先显示标题,同时下载<head>中的css和js文件,说到底是服务端和浏览器端并发处理页面展现.但Facebook如果仅做到这样的"提早flush",对性能的提升不会很明显.因为大量的时间消耗在<body>主体内容的生成时段.所以很自然想到需要分块flush,分块渲染.而Facebook页面恰恰可以分块:
我们看到Facebook分块后还有一个特点,就是各个区块的信息都是动态的,而动态信息都是根据userid查询得来,各个区块之间并没有关联,"好友动态"和"书签"完全没有关系.这样的特性可以带来另一个好处,就是服务端并发,Facebook首先取到当前用户的页面定制信息,生成布局,flush出去,然后在服务端就可以根据取到的用户已定制展现模块列表,并发请求各个数据中心,这样在后台各类应用可以按照统一接口,以区块划分,去除了各系统间的耦合.
原理再简单不过了,这就像在餐馆吃饭,先选好桌子点好菜(确定用户布局和要展现的模块),然后单子下到厨房后,多个大厨就可以同时上阵(服务端并发),做好一样端上来一样吃一样(客户端并发).这样从浏览器端到服务端,在接收数据的这1.5s左右时间之内,真正成了一个流水线.同样的那些限制条件也很好解释,吃快餐就完全没必要一盘菜取一次,每盘菜之间没什么关系.好处也显而易见,每个大厨做自己最擅长的,效率效果都会最优.
Bigpipe作为完整技术方案的若干技术细节:
我想任何了解flush(),chunked http,Javascript的开发者都能快速开发出Bigpipe的Demo.先输出布局HTML,包含若干pagelet(Facebook称页面区块为pagelet)的容器div.动态数据到达后输出一段inline script,里面包含响应的html串和一些配置信息等等,调用onPageletArrived()方法将html片段插入pagelet容器中.上段代码意思下:
- <body>
- <div id="layout">
- <div id="mod_profile"></div>
- <div id="mod_photo"></div>
- <div id="mod_friend"></div>
- </div>
- <?php flush();?><!--首先Flush页面布局-->
- <script>
- //html string for mod_photo
- </script>
- <?php flush();?><!--Flush #mod_photo的内容-->
- <script>
- //html string for mod_profile
- </script>
- <?php flush();?><!--Flush #mod_profile的内容-->
- <script>
- //html string for mod_friend
- </script>
- </body>
<body> <div id="layout"> <div id="mod_profile"></div> <div id="mod_photo"></div> <div id="mod_friend"></div> </div> <?php flush();?><!--首先Flush页面布局--> <script> //html string for mod_photo </script> <?php flush();?><!--Flush #mod_photo的内容--> <script> //html string for mod_profile </script> <?php flush();?><!--Flush #mod_profile的内容--> <script> //html string for mod_friend </script></body>
这里想讨论的是,使用这种方案会引入哪些其他问题,Facebook又是如何解决的.
脚本阻滞:
我们知道直接在html中引入外部脚本会造成页面阻滞,即使使用无阻脚本下载的一系列方法引入外部js,但因为JS单线程,当这些脚本load进来之后运行时也会发生阻滞.因为Facebook页面多样,依赖脚本大小不一,这些阻滞会对页面的快速展现造成影响.
Facebook做法是在ondomready之前只在头部输出一个很小的外部脚本,作为bigpipe的支撑.其余所有模块如果依赖外部脚本(比如某一模块需要日历控件支持),都会在domready事件之后加载.这样做即保证了所有页面所有模块都能在domready前快速形成展现,又可以保证无脚本阻滞的快速的domready时间.
最快可交互时间:
domready再快也至少是在页面第一个使用bigpipe输出的chunked的http请求完成之后,对于Facebook来说,这很可能是2秒之后了.那在这2s期间,如果只是页面展现了而不能交互(点击日历无反应),那方案依然不够完美.
Facebook的做法是,在domready后所依赖脚本已被加载之前,点击行为将会生成一个额外的脚本请求,只将这个点击所依赖的脚步预先load进来.这样保证了最快的可交互时间.Facebook在另一篇文章中对这个技术进行了详细的描述.
Bigpipe原理简单,实现不复杂,但Facebook却用了1年的时间才形成完备的解决方案.生成方案需要时间,而解决随之而来的脚本阻滞,保障最快交互时间等等问题也会消耗大量时间.
总结一下:
Bigpipe在Facebook获得巨大成功,我想在于对细节的深度挖掘.如果第一个请求时间不用那么长,那相当于去吃麦当劳还分两次拿薯条和汉堡.如果服务端不能并发,那肯定达不到如此好的优化效果.如果不排除外联脚本的下载和运行阻滞带来的不确定性,也得不到如此高的展现速度.同时如果不对domready前的click按需加载处理,可交互性会大打折扣.当然Facebook可能还有更多我们不知道的类似的技术细节,所有这些细节联合起来,才能得到这样漂亮的结果.
系统优化是个浩大的工程,在Yslow Rules这类规则默认遵守之后,要想再获得突破,那只能从自身的应用出发做选最合适的方案.同时Bigpipe出现不意味着Ajax过时了落伍了.页面的动态数据来自多个数据中心,那么用Bigpipe吧.如果一个页面上的动态数据可以用一个Ajax请求获得,那完全没必要用它.大多数应用第一个http请求的时间依然只用了很短的时间,所以Ajax还是主流.