实现最佳'fread'块大小?

时间:2011-05-18 18:16:52

标签: php disk fread

好吧,我知道我的问题并不完全具体,因为最佳的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,那么我很抱歉在这里发帖。如果你们需要我发布实际剧本的片段,我也可以这样做。

3 个答案:

答案 0 :(得分:3)

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);

默认情况下你甚至可以更慷慨,并根据你从sys_getloadavg()的第一个索引获得的值减少$speed,以避免给你的CPU带来压力。

答案 1 :(得分:0)

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

,而不是使用php来传输文件

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

答案 2 :(得分: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使用您的文件块而不是整个文件。