PHP的缓冲区你了解过吗

这篇文章是翻译自Julien Pauli的博客文章php output buffer in deep,Julien是PHP源码的资深开发和维护人员 。这篇文章从多个方面讲解了PHP中的输出缓冲区以及怎么使用它 。输出缓冲区可能一直都是PHP开发人员的一个盲点,很多人可能只是知道这个东西,而且也知道大概怎么使用,但对于它为什么是这个样子,以及还可能是其他什么样子,可能并不了解,这篇文章可以解决你的所有困惑!
引言
大家都知道PHP中有一个名为“输出缓冲区”层(layer)的东西 。这篇文章就是来讲解它到底是个什么东西的?PHP内部是怎么实现它的?以及在PHP程序中怎么使用它?这个层并不复杂,但经常会被误解,很多PHP开发者并没有完成掌握它 。今天我们就一起来彻底把它搞清楚吧 。
我们要讨论的东西是基于PHP 5.4(及以上版本),PHP中的OB层从5.4版开始就发生了很多变化,确切说是完全重写了,有些地方可能都不兼容PHP 5.3了 。
什么是输出缓冲区?
PHP的输出流包含很多字节,通常都是程序员要PHP输出的文本,这些文本大多是echo语句或者printf()函数输出的 。对于PHP中的输出缓冲区,你要知道三点内容 。
第一点是任何会输出点什么东西的函数都会用到输出缓冲区,当然这说的是用PHP写的程序 。如果你是编写PHP扩展,你使用的函数(C函数)可能会直接将输出写到SAPI缓冲区层,而不需要经过OB层 。你可以在源文件main/php_output.h中了解到这些C函数的API文档,这个文件给我们提供了很多其他的信息,例如默认的缓冲区大小 。
第二点你需要知道的是输出缓冲区层不是唯一用于缓冲输出的层,它实际上只是很多层中的一个 。最后一点你要记住输出缓冲区层的行为跟你使用的SAPI(web或cli)相关,不同的SAPI可能有不同的行为 。我们先通过一个图片来看看这些层的关系:

PHP的缓冲区你了解过吗

文章插图
 
上面这张图片展示了PHP中的三种缓冲区层的逻辑关系 。上面的两层就是我们通常所认识到的“输出缓冲区”,最后一个是SAPI中的输出缓冲区 。这些都是PHP中的层,当输出的字节离开PHP进入计算机体系结构中的更底层时,缓冲区又会不断出现(终端缓冲区(terminal%20buffer),fast-cgi缓冲区,web服务器缓冲区,OS缓冲区,TCP/IP栈缓冲区 。。。) 。请记住一个通用原则,除了这篇文章中讨论的PHP中的情况外,一个软件的很多部分都会先保留信息,然后再把它们传递到下一部分,直到最终把这些信息传递给用户 。
CLI的SAPI有点特殊,这里重点讲一下 。CLI会将INI配置中的output_buffer选项强制设置为0,这表示禁用默认PHP输出缓冲区 。所以在CLI中,默认情况下你要输出的东西会直接传递到SAPI层,除非你手动调用ob_()类函数 。并且在CLI中,implicit_flush的值也会被设置为1 。我们经常会搞不清implicit_flush的作用,源代码已说明一切:当implicit_flush被设置为打开(值为1),一旦有任何输出写入到SAPI缓冲区层,它都会立即刷新(flush,意思是把这些数据写入到更低层,并且缓冲区会被清空) 。换句话说就是:任何时候当你写入任何数据到CLI%20SAPI中时,CLI%20SAPI都会立即将这些数据扔到它的下一层去,一般会是标准输出管道,write()和fflush()这两个函数就是负责干这个事情的 。简单,对吧!
默认PHP输出缓冲区
如果你使用不同于CLI的SAPI,像PHP-FPM,你会用到下面三个跟缓冲区相关的INI配置选项:
output_bufferingimplicit_flushoutput_handler在搞清楚这几个选项的含义之前,有一点需要先说明下,不能在运行时使用ini_set()改这几个选项的值 。这些选项的值会在PHP程序启动的时候,还没有运行任何脚本之前解析,所以也许在运行时可以使用ini_set()改变它们的值,但改变后的值并不会生效,一切都已经太迟了,因为输出缓冲区层已经启动并已激活 。你只能通过编辑php.ini文件或者是在执行PHP程序的时候使用-d选项才能改变它们的值 。
【PHP的缓冲区你了解过吗】默认情况下,PHP发行版会在php.ini中把output_buffering设置为4096个字节 。如果你不使用任何php.ini文件(或者也不会在启动PHP的时候使用-d选项),它的默认值将为0,这表示禁用输出缓冲区 。如果你将它的值设置为“ON”,那么默认的输出缓冲区的大小将是16kb 。你可能已经猜到了,在web应用环境中对输出的内容使用缓冲区对性能有好处 。默认的4k的设置是一个合适的值,这意味着你可以先写入4096个ASCII字符,然后再跟下面的SAPI层通信 。并且在web应用环境中,通过socket一个字节一个字节的传输消息的方式对性能并不好 。更好的方式是把所有内容一次性传输给服务器,或者至少是一块一块地传输 。层与层之间的数据交换的次数越少,性能越好 。你应该总是保持输出缓冲区处于可用状态,PHP会负责在请求结束后把它们中的内容传输给终端用户,你不用做任何事情 。


推荐阅读