15

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

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 处理 PHP 输出的方式有关,因为 Apache 2 保留在原地。

4

5 回答 5

13

ob_flush() (an flush()) 只刷新 PHP 缓冲区——网络服务器自己维护一个缓冲区。听起来很奇怪,过早刷新缓冲区实际上会降低服务器的吞吐量,因此最新版本的 apache 缓冲区更加激进。在使用 HTTP 分块编码时,还存在与压缩和部分渲染相关的可怕问题。

如果您想逐步向页面添加内容,请使用 ajax 或 websockets 一次添加一点。

于 2012-12-06T22:56:09.190 回答
6

此问题与您的服务器(apache)有关,而不是与 php 版本有关。

一种选择是禁用输出缓冲,尽管站点其他部分的性能可能会受到影响

在阿帕奇

从您的服务器配置中设置 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

于 2015-08-22T11:33:51.377 回答
5

原始问题中描述的最有可能的变化是新设置使用的是 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 缓冲 - 添加php_value output_buffering off.htaccess

  3. 禁用缓冲通货紧缩的模组 - 禁用 mod_deflate 和 mod_gzip

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

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

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

于 2015-08-22T13:22:58.543 回答
4

这个问题有一个可能的解决方法,不需要您编辑现有脚本或修改服务器配置以停止缓冲输出。通过使用包装脚本,您可以在后台从服务 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。

于 2015-08-22T20:17:07.123 回答
3

我尝试了一切以使其正常工作,包括上面列出的所有已知设置。我一直在尝试使用 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();
}

希望这对某人有帮助!

于 2017-07-18T08:52:43.877 回答