展开“尽早刷新输出”话题之前,我们先来看看上图二个页面请求http瀑布图。第一个页面请求瀑布图,想必大家更常见到。即,直到index.php请求完毕之后,才开始请求a.js, b.js等资源。第二个页面改进了这种情况,没等index.php响应数据全部接受完毕就开始请求a.js、b.js等资源,减少了整个页面的挂钟时间。这得益于http1.1引入了块编码(Chunked Encoding),使得服务器不必等到脚本运行结束,就可是先把一块(部分)输出交给浏览器渲染。
php版 尽早输出
PHP提供了函数flush(),将当前已有的输出刷新给浏览器。这样,在flush()调用之前的输出,被作为一个数据块,立即从服务器发送给浏览器渲染。有时候,即使调用了flush()函数,也并没有那么管用。因为,很有可能PHP开启了输出缓冲机制(关于php输出缓冲,请参考这里)。如果启用了php输出缓冲机制,就需要通过ob_*函数先将output buffering中的数据刷新到STDOUT中。上图对应的代码如下
//file:index_src.php /* 没有使用刷新输出技术 */
<html> <head> <script type='text/javascript' src='a.js'></script> <script type='text/javascript' src='b.js'></script> </head> <body> hello world <?php sleep(1); ?> hello perfgeeks!~ </body> </html> <script type='text/javascript' src='c.js'></script>
/* --------------------------- */ //file:index.php /* 使用了刷新输出技术 */
<?php //彻底清除php的output_buffering while (ob_get_level() > 0) { ob_end_flush(); } ob_start(); //开启一个php output buffering ?> <html> <head> <script type='text/javascript' src='a.js'></script> <script type='text/javascript' src='b.js'></script> </head> <body> hello world <?php ob_flush(); //将php output buffering数据刷新到STDOUT flush(); //将STDOUT数据刷新到浏览器 sleep(1); ?> hello perfgeeks!~ </body> </html> <script type='text/javascript' src='c.js'></script>
请在适当的时机调用flush()函数。因为发送数据包会引起网络延迟,通常发送少量大数据包的效果要比发送大量小的数据包好,所以请不要过于频繁地调用flush()做不必要的刷新输出。
刷新输出失败
以下情况,有可能导致“刷新输出”失败。
- 数据还没有到达STDOUT,还在输出缓冲中,调用flush()刷新出去的是空数据。
- http响应头里面没有设置Transfer-Encoding:chunked,即http协议不支持块编码。http1.1才引入chunked encoding。http1.0不支持chunked encoding。目前大多数浏览器和web服务器都使用的是http1.1。
- 响应经过了Gzip压缩,被flush()出来的输出,并没有直接发送到浏览器,被压缩模块的缓冲机制缓存了输出。比如Apache mod_deflate的缓冲默认设置是8kb。
- 代理服务器,某些代理服务器会改变http响应设置,甚至会将http1.1降级到http1.0(不支持chunked encoding)。比如squid还不支持http1.1,它会将所有响应降级http1.0
- 浏览器域阻塞。IE6、IE7和FF2只对每个域建立同时最多建立二个连接(使用http/1.1的情况下是这样的,http/1.0会多一点)。大家注意,这个技术使用了块编码,html文档响应占用的这个连接还没有释放。当然,这种阻档是有很多技巧可以避免的。
- 浏览器延迟渲染。Chrome在接收数据未达到最小要求之前不会开始渲染,chrome大概是2kb,IE大概是255字节。
最后,附一幅来自yahoo.com的http瀑布图,它很明显使用了该优化技巧