4

申し分なく、最適な fread チャンク サイズは試行錯誤に基づくものであるため、私の質問が完全に具体的ではないことはわかっています。しかし、私は皆さんの何人かがこれに光を当てることができることを望んでいました.

これにはサーバー関連のものも含まれるため、Stackoverflow が完全に適切な場所であるかどうかはわかりませんが、ServerFault と比較してより良い選択であるように思われました。

まず、2 つのスクリーンショットを投稿します。

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

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

これで、PHP を使用してファイルをエンド ユーザーにストリーミングするスクリプトを作成できました。fopen と fread を使用してファイルをストリーミングします。これらのファイルのほとんどは 100MB を超えています。私の懸念は、サーバーの統計が上記のようになる場合があることです。2 つの画面は異なるサーバーのものです。どちらのサーバーも専用のファイル ストリーミング ボックスです。PHP がファイルをエンド ユーザーにストリーミングすることを除いて、それらでは何も実行されません。

私のサーバーが合計で約 4MB/秒のデータしかエンド クライアントに送信していない場合でも、ディスクの読み取りが 100M/秒以上であるという事実に混乱しています。この非常識なレベルの IO は、IO を待機し、タスクが山積みになるため、最終的に CPU をロックします。最終的にサーバーが完全に応答しなくなり、再起動が必要になります。

現在の 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

これで、PHPを使用してファイルをエンドユーザーにストリーミングするスクリプトができました。

実際に何が起こっているのかを明確にするために、Apacheが実際の「ストリーム」を担当します。PHPは、その出力についてApacheを直接処理します。したがって、PHPスクリプトのエンドユーザーはApacheです。次に、Apacheがユーザーへの出力を処理します。これは、明らかにあなたの場合は約4MB/秒です。ただし、Apacheにはその制限がなく、すべての出力を一度に取得して、クライアントへの遅延配信を処理できます。これを証明するには、ストリームが配信される前にスクリプトの終了を確認できる必要があります。スクリプトが向きを変えて別のファイルを配信しようとすると、サーバーリソースに対してApacheをキューに入れます。

より良い解決策は、アクセス可能なURLからのダウンロードをユーザーに要求させることにより、Apacheがファイル配信を完全に処理できるようにすることです。明らかに、これは静的コンテンツに限定されています。上記のスクリプトを修正するには、出力全体をバッファリングするのではなく、Apacheがチャンクを配信できるように、ファイルの読み取りの一部を遅らせる必要があります。

編集:メモリに問題がなく、スワップドライブアクティビティを除外できる場合は、単に同時ファイル読み取り要求である可能性があります。100MBで5つのファイルを要求すると、500MBの読み取りアクティビティになります。Apacheはスクリプトを抑制せず、実際にはすべての出力をバッファリングします。これは一度に100MBを超える可能性があります。これは、ディスクI / Oアクティビティの多くを説明します。これは、各要求によってファイル全体がバッファに読み込まれるためです。Alixが提案するスロットルを利用すると、より多くの同時リクエストが可能になりますが、最終的には制限に達します。ユーザーがApacheからデータを受信する速度がわからないため、ApacheとPHPがファイル全体ではなくファイルのチャンクを処理できるように、スロットルサイズのバランスをうまくとる必要がある場合があります。

于 2011-05-18T19:11:38.043 に答える
0

一般に、プリフェッチとファイルシステムのオーバーヘッドにより、実際の I/O がユーザー空間の I/O よりも高速になることがあります。ただし、サーバーがロックされることはありません。キャッシュ サイズは、1KiB から 16MiB の間であれば、ほとんどまたはまったく影響しません。ただし、php を使用してファイルをストリーミングする代わりに、より最適化されたreadfileを検討する必要があります。

そうは言っても、重大なプログラミング エラーがない限り、この動作はおそらく小さなループとは直接関係ありません。まず、iotop を使用して、実際に I/O を引き起こしているプログラムを特定する必要があります。php の場合 (同時実行スクリプトはいくつありますか? 申し訳ありませんが、スクリーンショットは完全に文字化けしており、有用な情報がほとんど表示されていないようです)、出力バッファリングを使用していることを除外し、メモリ消費量とさまざまな php チューニング パラメーターを確認してください。 (phpinfo に概要があります)。ところで、htop は top に代わる方法として優れています ;)。

于 2011-05-18T18:29:19.537 に答える