响应内容有时需要很长时间


Echoing content sometimes takes a very long time

我有一个脚本,构建我的网页在一个字符串($content),然后回显给用户。

我的脚本是这样的:

$time1= microtime(true);
$content = create_content();
$content_time=(microtime(true)-$time1)
$time = microtime(true);
echo $content;
$echo_time = (microtime(true)-$time);

现在$content_time总是低于0.5s,所以没有问题。然而,一天中有几次,$echo_time远远超过一秒,甚至可以达到15秒。内容不是很大,大约10-20kb,发生这种情况的时间是完全随机的,所以它不是在繁忙的时间,甚至在半夜发生。

有人知道那是什么吗?

编辑站点托管在(远程)专用服务器上,并且仅托管此站点。有一个数据库涉及到,但就像我说的$content_time是远远低于1秒,所以这个函数所做的不能是延迟。

当我的网站的时间超过一定的值(让我们说5s),我记录这个。甚至谷歌机器人有时似乎也有这些问题,所以我不认为他们使用拨号连接:)

让我们缩小问题范围,剔除一些因素…

在问题中,您指出您正在回显10-15kb。无论如何缓冲输出,这都是一个很大的量——记住php是单线程的,一旦清空缓冲区,就必须等待所有的输出通过shell或HTTP完成,然后脚本才能继续执行。它最终必须在继续回显之前刷新内部缓冲区。在没有echo

的刷新开销的情况下获得良好的时间

尝试替换

$time = microtime(true);
echo $content;
$echo_time = (microtime(true)-$time);

ob_start();
$time = microtime(true);
echo $content;
$echo_time = (microtime(true)-$time);
ob_clean();

这将回显到缓冲区,但实际上不会通过HTTP或其他方式输出。这应该会给你echo命令的"真实"时间,而不用担心发送缓冲区中的内容。

如果echo_time缩小了,那么你就有一个传输问题需要尽可能用缓冲来解决。

如果echo_time仍然太大,则需要开始深入研究PHP C代码。

无论哪种方式,你都更接近于找到你的问题和解决方案

From http://wonko.com/post/seeing_poor_performance_using_phps_echo_statement_heres_why

这个旧的bug报告可能会给我们一些启示。简而言之,使用echo向浏览器发送大字符串会导致糟糕的性能,因为内格尔算法导致数据在TCP/IP上传输时被缓冲。

解决方案吗?一个简单的三行函数,在回显之前将大字符串分成更小的块:

function echobig($string, $bufferSize = 8192) { 
    $splitString = str_split($string, $bufferSize);
    foreach($splitString as $chunk) { echo $chunk; }
}

尝试缓冲大小,看看什么最适合您。我发现8192除了是个不错的整数外,似乎也是个不错的数字。一些其他的值也可以,但是在几分钟的修修补补之后,我无法辨别出一个模式,而且显然有一些数学在起作用,我不想试图弄清楚。

顺便说一下,当使用PHP的输出控制函数(ob_start()和friends)时,性能也会受到影响

在OPs评论之后,他尝试了这个,我也发现了以下PHP.net建议str_split也可以浪费资源,echobig函数可以通过使用以下代码进一步优化:

function echobig($string, $bufferSize = 8192) {
  // suggest doing a test for Integer & positive bufferSize
  for ($chars=strlen($string)-1,$start=0;$start <= $chars;$start += $bufferSize) {
    echo substr($string,$start,$buffer_size);
  }
}

您是否尝试过使用CLI而不是通过Apache运行脚本?

使用输出缓冲区可能会更好。在基本级别上,使用ob_start()开始向输出缓冲区写入,然后使用ob_end_flush()将其推送到客户机。以下是php.net对ob_start()的评论:

该函数将打开输出缓冲。当输出缓冲处于活动状态时,脚本不会发送任何输出(除了报头),而是将输出存储在内部缓冲区中。这个内部缓冲区的内容可以使用ob_get_contents()复制到一个字符串变量中。要输出存储在内部缓冲区中的内容,请使用ob_end_flush()

我过去有过同样的问题,非常类似于你的。我发现这个问题可能是由于客户端速度慢造成的。如果客户端获取了页面的一半,然后挂起,php将等待,直到客户端准备好,然后发送其余的内容。所以你这边应该没问题。

更新:

您可以尝试在服务器上执行以下脚本来检查这一点。将此脚本放到服务器上,并命名为echo.php:

<?php
$time_start = time();
echo str_repeat("a", 200000);
echo "'nThis script took: " . (time() - $time_start) . " sec";

然后用这个脚本获取它(将example.com更改为您的域名):

<?php
$fp = fsockopen("example.com", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />'n";
} else {
    $out = "GET /echo.php HTTP/1.1'r'n";
    $out .= "Host: example.com'r'n";
    $out .= "Connection: Close'r'n'r'n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 5000);
        sleep(1);
    }
    fclose($fp);
}

我有echo.php运行27秒。当我删除sleep(1)行时,echo.php只需要2秒即可运行。

因为不知道create_content()函数的主体就不可能告诉你原因,我建议你直接在这个函数中添加更多的"时间记录"函数。使包含的代码越来越少,您将最终找到导致延迟的行。了解具体的行将帮助您理解问题(数据库、机器负载、到外部服务的连接问题等)。

脚本中有while()或for()循环吗?如果是这样,您应该检查这些值是否与任何东西冲突,偶尔我自己会忘记这些,我的脚本也会运行大约30秒。

我的猜测是访问这么大的字符串的行为在多次使用中占用了相当多的内存。因为PHP是垃圾回收的,所以内存会被占用,直到垃圾回收器运行,然后才会被释放。我的猜测是在字符串变量中存储内容的多个请求导致快速填充易失性内存(RAM)。然后一天中有几次,你开始达到极限,导致加载时间变慢。垃圾回收器启动,一切恢复正常。

如果这是专用服务器,请登录控制台,查看生成内容时哪个进程使用了大量cpu时间。当我们看不见代码时,很难分辨。也许你只是需要一些索引在数据库中,或者你应该删除一些索引。

您也可以查看httpd和mysqld日志文件