PHP输出缓冲区不冲洗

我有许多脚本在执行时回显进度,因为它们长时间运行。 在处理的每一个循环的数据行的末尾,每个基本上都做了以下处理:

echo '.'; @ob_flush(); flush(); 

这工作很好多年,然后我升级到几个服务器的PHP 5.3.x和Apache 2.2.x。 现在,即使我使用空格填充缓冲区或设置“ob_implicit_flush(1)”,我也无法得到它显示命令的输出。

一台服务器仍显示输出,但它是分块的。 这可能需要将近5分钟,然后屏幕上突然出现一串点。 与其他服务器,我得到什么,直到脚本完全执行完成。

我已经尝试通过php.ini和httpd.conf文件来查看是否可以找出不同服务器之间发生了什么变化,但我明显错过了一些东西。

我也试过在受影响的脚本的.htaccess中禁用mod_deflate,但是这也没有帮助(禁用mod_gzip用于立即解决问题)。

有人可以用这个指向正确的方向吗? 不能够实时监控脚本执行是造成各种各样的问题,但我们不能再留在这些旧的PHP版本。

更奇怪的是,我尝试将服务器降级到PHP 5.2.17,但输出缓冲区问题在降级后依然存在。 这让我怀疑这是Apache自从Apache 2处理PHP输出的方式。

ob_flush()(一个flush())只能刷新PHP缓冲区 – webserver自己维护一个缓冲区。 也许听起来很奇怪,尽早刷新缓冲区实际上会降低服务器的吞吐量,因此最近版本的apache缓冲区更为激进。 在使用HTTP分块编码时,还存在与压缩和局部呈现相关的可怕问题。

如果你想递增地添加内容到一个页面,然后使用Ajax或websockets一次添加一点。

原来问题中所描述的更改很可能是新安装程序使用FastCgi( http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html ),并且已将缓冲打开为默认设置。

但还有其他因素需要检查:

如果你正在使用Fcgid,那么这也有缓冲: http ://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidoutputbuffersize

如果你的PHP和Apache之间的字符集不匹配 – 这可以容纳一些东西。

mod_deflate和mod_gzip也是缓冲区(如原始问题所述)

所以要检查的步骤是:

  1. 刷新PHP缓冲区 – (如问题中所述)

  2. 关闭Apache缓冲 – 在.htaccess中添加php_value output_buffering off

  3. 禁用缓解通​​货紧缩的mod – 禁用mod_deflate和mod_gzip

  4. 确保PHP和Apache之间的char编码匹配 – 添加default_charset = "utf-8"; 在httpd.conf中的php.ini和AddDefaultCharset utf-8中)

  5. 在FastCgi或Fcgid中禁用缓冲 – 您可以通过添加-flush选项来禁用FastCgi中的缓冲。 上面的链接中的细节。 上面也列出了Fcgid的选项。

据我所知,这些是服务器上唯一的缓冲区。 显然,服务器和浏览器之间的其他设备也可以缓冲,例如,代理可以在传递之前等待提供完整的输出。 提琴手( https://www.telerik.com/fiddler )这样做,它经常让我躺下,直到我记得。

有一个可能的解决此问题,不需要您编辑您的现有脚本或修改您的服务器配置来停止缓冲输出。 通过使用包装脚本,您可以从服务于Web请求的PHP脚本中在后台启动您的长时间运行的进程。 然后,您可以将长时间运行的进程的输出传输到可轻松读取的文本文件中,以便通过轮询来查找脚本的当前进度。 示例如下:

长时间运行的进程脚本

 <?php // long_process.php echo "I am a long running process "; for ($i = 0; $i < 10; $i++) { echo "."; sleep(1); } echo " Processing complete"; ?> 

脚本初始化长时间运行的进程并观察输出

 <?php // proc_watcher.php $output = './output.txt'; if ($_GET['action'] == 'start') { echo 'starting running long process<br>'; $handle = popen("nohup php ./long_process.php > $output &", 'r'); pclose($handle); } else { echo 'Progress at ' . date('H:i:s') . '<br>'; echo file_get_contents($output); } $url = 'proc_watcher.php'; ?> <script> window.setTimeout(function() { window.location = '<?php echo $url;?>'; }, 1000); </script> 

如果您发送一个web请求到proc_watcher.php?action=start脚本,应该在后台启动长时间运行的进程,然后每秒钟将输出文件的内容返回到Web浏览器。

这里的技巧是命令行nohup php ./long_process.php > ./output.txt & ,它在后台运行进程,并将输出发送到文件而不是STDOUT。

这个问题更多的是与你的服务器(Apache),而不是PHP版本。

一个选项是禁用输出缓冲,虽然性能可能在网站的其他部分受到影响

在Apache上

从服务器配置中设置php ini指令( output_buffering=off ),包括.htaccess文件。 所以我在.htaccess文件中使用了以下内容来为那个文件禁用output_buffering:

 <Files "q.php"> php_value output_buffering Off </Files> 

然后在我的静态服务器配置中,我只需要AllowOverride Options=php_value (或更大的锤子,如AllowOverride All ),以便在.htaccess文件中允许这样做。

在Nginx上

要为Nginx禁用缓冲(将“proxy_buffering off”添加到配置文件并重新启动Nginx

我尝试了所有的工作,包括上面列出的所有已知的设置。 我一直在试图使用PHP服务使用HTTP_RANGE分块的视频文件,它不工作。

拉出大部分头发后,我找到了答案:你必须输出至少一个比缓冲区大小多的字节,才能输出到浏览器。 这是最后为我工作的脚本:

 <?php // Close open sessions session_write_close(); // Turn off apache-level compression @apache_setenv('no-gzip', 1); // Turn off compression @ini_set('zlib.output_compression', 0); // Turn error reporting off @ini_set('error_reporting', E_ALL & ~ E_NOTICE); // Tell browser not to cache this header("Cache-Control: no-cache, must-revalidate"); // close any existing buffers while (ob_get_level()) ob_end_clean(); // Set this to whatever you like $buffer = 8096; for($i = 1; $i <= 8; $i++) { // Start a output buffer with specified size ob_start(null,$buffer,PHP_OUTPUT_HANDLER_FLUSHABLE); // Output exactly one byte more than that size // \n == 2 bytes, so 8096-1+2 = 8097 echo str_repeat('=', $buffer-1)."\n"; // 0.25s nap usleep(250000); // End output buffering and flush it ob_end_flush(); flush(); } 

希望这可以帮助别人!