PHP output buffer – bigpipe基础

本文主要介绍PHP使用PHP-FPM SAPI时候的输出缓冲区。
(最近在复习之前在微博使用的Bigpipe技术,想起了ob_flush和flush函数,当时只记得是这2个函数要组合使用,也一直没有搞得特别透彻,趁着这2天有时间,研究了下^_^)
本文使用LNMP环境,其中版本信息:
nginx version: nginx/1.9.1
其中nginx中关于fastcgi buffer的设置参数为(为了便于展示效果,暂时关闭gzip压缩):

在五中介绍fastcgi_buffer的时候,会开启gzip压缩。
PHP 5.6.10
当然PHP的版本>=5.4即可,nginx作为server使用,并无限制,使用较新版本即可。

一、LNMP架构中的输出缓冲区

我们首先来看一下LNMP架构中涉及输出缓冲区的部分,后面会依次验证:
LNMP输出缓冲区架构图

LNMP输出缓冲区架构图

从图中,我们可以看出 Nginx层,SAPI层(PHP-FPM)和PHP内核中均有输出缓冲区,由此可见缓冲区是多么重要的一部分,接下来,我们依次介绍这几部分缓冲区。

二、PHP-FPM 输出缓冲区

OK,我们首先来探讨下PHP-FPM 中的输出缓冲区(这玩意迷惑了我不少时间-_-),我们先来设置一个ini配置: output_buffering,我们把他设置为 0:

设置完这个参数后,我们便关闭了PHP内核中的默认缓冲区。
我们来看一段代码,因为nginx中设置的fastcgi buffer的大小是4k,而我们每次输出的数据都是大于4k的,因此输出不会受到nginx输出缓冲区的影响。
(1) 未使用任何flush函数

运行这段代码,我们的预期是每次输出1个数字,可是结果却是每次输出2个数字,即0,1一起输出,2,3一起输出,这是为什么呢?
由于我们关闭了PHP内核中的输出缓冲,也没有使用ob_start系列的函数,而且数据量也大于了nginx中的输出缓冲,所以我猜测PHP-FPM中还有一个输出缓冲区,通过查阅fpm的源代码(php-5.6.10/sapi/fpm/fpm/fastcgi.h文件),我发现在fcgi_request结构中有一个out_buf参数,大小是8k:

于是我觉得这个参数应该就是PHP-FPM中的输出缓冲区大小。
OK,让我们来验证一下,我们将上面代码中空格的数目改为8192试试,即:

重新请求该页面,果然,现在是每次输出1个数字了,和我预料的一样。
(2) 使用ob_flush函数:

重新请求上面的页面,发现还是每次输出2个数字,说明ob_flush不能刷新PHP-FPM缓冲区

(3) 使用flush函数:

重新请求上面的页面,每次输出1个数字,说明flush函数可以刷新PHP-FPM缓冲区
至此,我们已经了解了LNMP输出缓冲区架构图中 PHP-FPM Output Buffer的部分了。

三、PHP内核默认缓冲区

了解了PHP-FPM中的缓冲区后,我们来看看PHP内核中的输出缓冲区。
我们将ini配置中output_buffering设置为16384,即16k,这主要是为了避开PHP-FPM中缓冲区的影响,毕竟PHP-FPM中的缓冲区大小是硬编码的,我们没法修改配置,只能规避。
(1) 未使用任何flush函数
我们重新请求二(1)中的代码, 页面中每次输出4个数字,说明output_buffering的设置起了作用,4个数字的输出结果就是16k+。
(2) 使用ob_flush函数
我们重新请求二(2)中的代码,页面中每2个数字输出一次,可见output_buffering已经不起作用了,现在只有PHP-FPM中的缓冲区起作用,由此说明ob_flush函数可以刷新PHP内核中的默认输出缓冲区
(3) 使用flush函数
我们重新请求二(3)中的代码,页面中每4个数字输出一次,这说明flush函数不能刷新PHP内核中默认的输出缓冲区
(4) 同时使用flush和ob_flush函数,即:

正如我们前面已经得到的结论,ob_flush可以刷新PHP内核中的默认缓冲区,flush可以刷新PHP-FPM中的缓冲区,那么这2个函数一起使用,就可以得到预期的效果了:每次输出1个数字。
至此,我们已经了解了LNMP输出缓冲区架构图中default Ob到PHP-FPM的部分了。

四、PHP内核用户自定义缓冲区

PHP内核中,除了默认的输出缓冲区之外,用户也可以自己定义输出缓冲区,也就是ob_系列函数了。
(1)、我们可以通过ob_start函数激活一个缓冲区,激活的缓存区被压入栈中(OG(handlers)),我们多次调用ob_start函数会生成一个缓冲区栈。
开启output_buffering配置后,PHP内核默认输出缓冲区处于OG(handlers)的栈底。

(2)、我们通过ob_get_contents函数可以获取OG(handlers)栈顶部缓冲区(我们称之为active)的内容。
(3)、我们通过ob_flush函数可以将active缓冲区的内容冲刷到active缓冲区的上一级缓冲区(栈顶的下一位),如果active已经处于栈底,那么冲刷active缓冲区会将其内容输出到SAPI(正如我们在三中所看到的)。
(4)、我们通过ob_end_flush函数除了将active缓冲区的内容冲刷出去外,还会将当前缓冲区从栈顶弹出。

从(3)(4)中我们可以看出,如果我们在代码中使用了嵌套的ob_start,如果我们还希望,调用完ob_flush和flush的组合之后,就能将PHP中缓冲区的内容输出给Nginx,那么代码中的ob_start函数的数目和ob_end_相关函数的数目应该是一致的,如下简单示例:

函数仅用于示例,没有实际意义….
至此,我们已经了解了LNMP输出缓冲区架构图中User Ob的部分。

五、Nginx fastcgi 缓冲区

之前我们都是关闭了gzip,因为gzip后,数据小于fastcgi的buffer大小,这样的话就没法达到我们想要的效果,但是关闭gzip就起不到压缩数据的目的了,而且在整个数据的生成过程中,已经有多个buffer了,因此我们可以关闭nginx的fastcgi buffer了,如下:

运行如下的一段代码:

这段代码是可以达到我们的效果的: 每s输出一个数字!
当然,直接关闭fastcgi_buffer是很不灵活的,因为毕竟有很多请求是不使用bigpipe模式的,还好nginx为我们提供了一个header头: X-Accel-Buffering,使用这个header头可以不使用buffer,如下:

这样在开启fastcgi_buffer和gzip的情况下面也可以每s输出一个数字。
在代码中我们始终有补全4k个空格的操作,这是因为有些浏览器接收到这么多数据的时候才会渲染,我在mac中的chrome中测试,当使用了X-Accel-Buffering头的时候,即使不补全空格,也是可以实现我们的效果的,但是在windows中的IE浏览器中不行。

六、implicit_flush配置和ob_implicit_flush函数

其中,如果将php.ini配置中的implicit_flush设置为1, 或者调用ob_implicit_flush(true),那么就不需要调用flush函数了,他们都会刷新PHP-FPM缓冲区中的内容。

本文中主要讨论了SAPI使用PHP-FPM时的输出缓冲区情况,对于别的SAPI,需要区别对待,比如CLI,他的implicit_flush硬编码为1,他的output_buffering也是设置为0。

本人在研究PHP Output Buffer的过程中,通过下面这篇文章学习较多,再次感谢:
PHP output buffer in deep

发表评论

电子邮件地址不会被公开。 必填项已用*标注