4

好吧,我知道我的问题并不完全具体,因为最佳 fread 块大小更多是基于试错的事情。但是,我希望你们中的一些人可以对此有所了解。

这也涉及服务器相关的东西,所以不确定 Stackoverflow 是否完全正确,但与 ServerFault 相比,它似乎是一个更好的选择。

首先,我将发布两个屏幕截图:

http://screensnapr.com/e/pnF1ik.png

http://screensnapr.com/e/z85FWG.png

现在我有了一个脚本,它使用 PHP 将文件流式传输给最终用户。它使用 fopen 和 fread 流式传输文件。大多数这些文件都大于 100MB。我担心的是,有时,以上就是我的服务器统计信息。两个屏幕来自不同的服务器;两台服务器都是专用的文件流媒体盒。除了 PHP 将文件流式传输给最终用户之外,没有其他任何东西可以在它们上面运行。

我对这样一个事实感到困惑,即使我的服务器仅向最终客户端传输总计约 4MB/秒的数据,磁盘读取速度也在 100M/s 及以上。这种疯狂的 IO 级别最终会锁定我的 CPU,因为它等待 IO 和任务堆积;最终我的服务器变得完全没有响应,需要重新启动。

我当前的 fread 块大小设置为8 * 1024。我的问题是,改变块大小和试验是否有帮助?客户端仅以平均约 4MB/秒的速度下载数据。那么为什么磁盘以 100MB/秒的速度读取数据呢?我已经在服务器端尝试了所有可能的解决方案;我什至用新磁盘交换了磁盘以排除潜在的磁盘问题。在我看来这是一个脚本问题;也许 PHP 正在从磁盘读取整个数据,而不管它向最终客户端传输了多少数据?

任何帮助将不胜感激。如果这属于 ServerFault,那么我很抱歉在这里发帖。如果你们需要我从实际脚本中发布片段,我也可以这样做。

4

3 回答 3

4

8 * 1024字节?这似乎完全合理,如果是这样,您的高磁盘 I/O 可能与并发请求有关。您是否考虑过实施某种带宽限制?这是我为我的框架phunction做的一个仅 PHP 的实现:

public static function Download($path, $speed = null, $multipart = false)
{
    if (strncmp('cli', PHP_SAPI, 3) !== 0)
    {
        if (is_file($path) === true)
        {
            while (ob_get_level() > 0)
            {
                ob_end_clean();
            }

            $file = @fopen($path, 'rb');
            $size = sprintf('%u', filesize($path));
            $speed = (empty($speed) === true) ? 1024 : floatval($speed);

            if (is_resource($file) === true)
            {
                set_time_limit(0);
                session_write_close();

                if ($multipart === true)
                {
                    $range = array(0, $size - 1);

                    if (array_key_exists('HTTP_RANGE', $_SERVER) === true)
                    {
                        $range = array_map('intval', explode('-', preg_replace('~.*=([^,]*).*~', '$1', $_SERVER['HTTP_RANGE'])));

                        if (empty($range[1]) === true)
                        {
                            $range[1] = $size - 1;
                        }

                        foreach ($range as $key => $value)
                        {
                            $range[$key] = max(0, min($value, $size - 1));
                        }

                        if (($range[0] > 0) || ($range[1] < ($size - 1)))
                        {
                            ph()->HTTP->Code(206, 'Partial Content');
                        }
                    }

                    header('Accept-Ranges: bytes');
                    header('Content-Range: bytes ' . sprintf('%u-%u/%u', $range[0], $range[1], $size));
                }

                else
                {
                    $range = array(0, $size - 1);
                }

                header('Pragma: public');
                header('Cache-Control: public, no-cache');
                header('Content-Type: application/octet-stream');
                header('Content-Length: ' . sprintf('%u', $range[1] - $range[0] + 1));
                header('Content-Disposition: attachment; filename="' . basename($path) . '"');
                header('Content-Transfer-Encoding: binary');

                if ($range[0] > 0)
                {
                    fseek($file, $range[0]);
                }

                while ((feof($file) !== true) && (connection_status() === CONNECTION_NORMAL))
                {
                    ph()->HTTP->Flush(fread($file, round($speed * 1024)));
                    ph()->HTTP->Sleep(1);
                }

                fclose($file);
            }

            exit();
        }

        else
        {
            ph()->HTTP->Code(404, 'Not Found');
        }
    }

    return false;
}

上面的方法有一些小的依赖,它增加了一些不必要的功能,比如多部分下载,但你应该能够毫无问题地重用节流逻辑。

// serve file at 4 MBps (max)
Download('/path/to/file.ext', 4 * 1024);

您甚至可以在默认情况下更加慷慨,并$speed根据您从第一个索引中获得的值减少sys_getloadavg()以避免对 CPU 造成压力。

于 2011-05-18T19:09:36.740 回答
0

通常,由于预取和文件系统开销,实际 I/O 可能比用户空间 I/O 更快。但是,这绝不应该锁定您的服务器。只要缓存大小在 1KiB 到 16MiB 之间,缓存大小对此几乎没有影响。但是,不要使用 php 来流式传输文件,而应该考虑优化得多的readfile

话虽如此,除非出现严重的编程错误,否则此行为可能与您的小循环没有直接关系。首先,您应该使用 iotop 找出实际导致 I/O 的程序。如果是 php (有多少并发脚本?抱歉,截图似乎完全乱码,几乎没有显示有用的信息),排除你正在使用输出缓冲并查看内存消耗以及各种 php 调整参数(phpinfo 有一个很好的概述)。顺便说一句,htop 是一种更好的替代 top ;)。

于 2011-05-18T18:29:19.537 回答
0

现在我有了一个脚本,它使用 PHP 将文件流式传输给最终用户。

只是为了澄清真正发生的事情,Apache 负责实际的“流”。PHP 直接处理 Apache 的输出。因此,PHP 脚本的最终用户是 Apache。然后 Apache 处理用户的输出,在你的情况下显然是大约 4MB/秒。然而,Apache 没有这种限制,可以一次获取所有输出,然后处理延迟交付给客户端。为了证明这一点,您应该能够在传输流之前看到您的脚本退出。如果您的脚本转而尝试传递另一个文件,那么您将 Apache 与您的服务器资源排队。

更好的解决方案可能是允许 Apache 通过让用户从可访问的 url 请求下载来完全处理文件传递。显然,这仅限于静态内容。要修复上述脚本,需要延迟一些文件读取以允许 Apache 交付块而不是缓冲整个输出。

编辑:如果您的记忆很好并且我们可以排除交换驱动器活动,那么它可能只是并发文件读取请求。如果我们以 100mb 请求 5 个文件,那么这就是 500mb 的读取活动。Apache 不会限制您的脚本,实际上会缓冲所有输出,一次可能超过 100mb。这会导致大量的磁盘 i/o 活动,因为每个请求都会将整个文件读入缓冲区。使用 Alix 建议的节流阀将允许更多并发请求,但最终你会达到一个限制。我们无法确定用户从 Apache 接收数据的速度有多快,因此您可能必须为节流大小找到一个很好的平衡点,以允许 Apache 和 PHP 处理您的文件块而不是整个文件。

于 2011-05-18T19:11:38.043 回答