PHP的readfile()永远不会结束并使Apache服务器挂起。

9

我有一个与Apache服务器和PHP应用程序相关的大问题。

该服务器正在提供一个相当高流量的网站,该网站正在使用PHP运行。

每24个或48个小时,Apache都会挂起,我必须重新启动它才能再次访问该网站。我必须重新启动它,因为Apache达到了允许的进程/服务器的最大数量(对我来说是16000),而它无法释放其他进程,因为其他进程都是活动状态。

托管在此服务器上的网站是一个PHP应用程序,它在最后提供文件:假设它是一个下载服务器。

浏览器通过提交POST请求的表单请求文件。

问题在于,这个POST请求似乎永远不会结束(我可以看到我的服务器状态中几乎所有的16000个进程都是POST请求)。

要服务的文件很大(10M到2G),我使用PHP readfile函数来服务它们(我不想使用href链接来服务它们,所以我使用一个form POST请求,这样用户就看不到文件在我的文件系统中的位置)。

即使我在其结尾处使用exit(),使用PHP readfile的函数似乎永远不会结束(请参见下面的代码片段)。

我在这里寻求一种避免这些由我的PHP代码引起的永远不会结束的POST请求的方法。我希望保留POST文件服务方式。

首先是我的配置:

  • Ubuntu服务器14.04
  • Apache 2.4与MPM prefork
  • PHP 5.5.9 (mod PHP)
  • 硬件:128G RAM

我的mpm_prefork.conf文件:

<IfModule mpm_prefork_module>
        StartServers              512
        MinSpareServers           512
        MaxSpareServers          1024
        ServerLimit             16000 # no problem with my server ram
        MaxRequestWorkers       16000
        MaxConnectionsPerChild  10000
</IfModule>

我的 apache2.conf 文件:

...
Timeout 300
KeepAlive On
MaxKeepAliveRequests 500
KeepAliveTimeout 5
...

我的php.ini文件:

max_execution_time = 7200

我的Apache日志文件:对于我的问题没有任何有趣的内容。

一个Munin图表显示问题发生的时间: enter image description here

我的Apache服务器状态看起来像这样: enter image description here

enter image description here

还有我的服务器类(导致问题的代码):

class Server
{
    /* the file is served from a remote url source */
    public function serveFileFromUrl()
    {
        if (empty($_POST)) {
            return;
        }

        $url  = $_POST['file_url'];
        $mime = $_POST['mime'];
        $name = sanitizeFileName($_POST['name']) . uniqid() . '.' . $mime;
        $size = $_POST['size'];

        if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false) {

            // for Internet Explorer
            if ($mime == 'mp3') {
                header('Content-Type: "audio/' . $mime . '"');
            } else {
                header('Content-Type: "video/' . $mime . '"');
            }
            header('Content-disposition: attachment; filename="' . $name . '"');
            header('Expires: 0');
            if ($size !== '') {
                header('Content-Length: ' . $size);
            }
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header("Content-Transfer-Encoding: binary");
            header('Pragma: public');

        } else {

            // not for internet Explorer
            if ($mime == 'mp3') {
                header('Content-Type: "audio/' . $mime . '"');
            } else {
                header('Content-Type: "video/' . $mime . '"');
            }
            header('Content-disposition: attachment; filename="' . $name . '"');
            header('Expires: 0');
            if ($size !== '') {
                header('Content-Length: ' . $size);
            }
            header("Content-Transfer-Encoding: Binary");
            header('Pragma: no-cache');

        }

        ob_end_clean(); // fix memory problems with readfile (http://heap.tumblr.com/post/119127049/a-note-about-phps-output-buffer-and-readfile)
        flush();        // fix memory problems with readfile
        readfile($url); 
        @ob_end_flush();
        exit();
    }

    /* file is served from my filesystem */
    public function serveFileFromPath()
    {
        if (empty($_POST)) {
            return;
        }

        $url  = APP_PATH . '/download/' . $_POST['file_name'];
        $mime = $_POST['mime'];
        $name = sanitizeFileName($_POST['name']) . '-' . uniqid() . '.' . $mime;
        $size = $_POST['size'];

        if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false) {

            // for Internet Explorer
            if ($mime == 'mp3') {
                header('Content-Type: "audio/' . $mime . '"');
            } else {
                header('Content-Type: "video/' . $mime . '"');
            }
            header('Content-disposition: attachment; filename="' . $name . '"');
            header('Expires: 0');
            if ($size !== '') {
                header('Content-Length: ' . $size);
            }
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header("Content-Transfer-Encoding: binary");
            header('Pragma: public');

        } else {

            // not for internet Explorer
            if ($mime == 'mp3') {
                header('Content-Type: "audio/' . $mime . '"');
            } else {
                header('Content-Type: "video/' . $mime . '"');
            }
            header('Content-disposition: attachment; filename="' . $name . '"');
            header('Expires: 0');
            if ($size !== '') {
                header('Content-Length: ' . $size);
            }
            header("Content-Transfer-Encoding: Binary");
            header('Pragma: no-cache');

        }

        ob_end_clean(); // fix memory problems with readfile (http://heap.tumblr.com/post/119127049/a-note-about-phps-output-buffer-and-readfile)
        flush();        // fix memory problems with readfile
        readfile($url);
        @ob_end_flush();
        exit();
    }
}

是否有解决方法可以避免无休止的POST请求?如果能解决这个问题,我可以通过其他方式来提供文件。

请不要重复,我已经添加了足够的代码、配置片段和图片以使这个问题更具体 :)


2
一种简单的方法来减少50%的代码是用HTTP重定向(即头部Location)替换serveFileFromUrl()部分。如果幸运的话,这甚至可能解决您的问题。 - Calimero
1
你已将时间限制设置为0。不要这样做。这绝对不是一个解决方法,但请将其设置为10小时左右,即使像现在这样出现故障,它也会保持服务器的运行,并且很可能不会干扰您的操作。将其设置为您认为脚本需要的时间的两倍。 - Elentriel
@Calimero 如果我只使用头文件 Location,那么文件(这是一个视频文件)将在浏览器中播放。我希望文件被下载。 - hedi
@Elentriel 好的,我已经将它修改为14400(4小时),我会看看是否有帮助。谢谢。 - hedi
尝试添加error_reporting(-1);。调用您的Server类的是什么,我们确定它是否到达那里,而不是在那之前挂起? - Drazisil
显示剩余2条评论
1个回答

3

max_execution_time已经在我的php.ini文件中设置为2小时。我已经更新了问题。 - hedi
你应该真正考虑使用mod_xsendfile来解决你的问题。这里有另一篇关于它的文章:http://codeutopia.net/blog/2009/03/06/sending-files-better-apache-mod_xsendfile-and-php/ - user5542121
我会看一下,谢谢。我两周前尝试过但失败了:它只提供0kb的文件。 - hedi
1
我已将您的答案标记为已接受,因为我重新安装了带有nginx的服务器,并且现在使用x-accel-redirect,这是nginx中与apache mod_xsendfile相当的功能。现在没有问题了,谢谢。 - hedi
1
max_execution_time在其计算中不包括网络和数据库调用。 - Nikolay Shindarov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接