2011-05-18 41 views
2

好吧,我知道我的問題並不完全具體,因爲最佳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,那麼我很抱歉在這裏發佈。如果你們需要我從實際腳本中發佈片段,我也可以這麼做。

+0

可能有多個併發文件流會話。您需要限制併發請求的最大數量。 – 2011-05-18 18:23:54

+0

我明白了。這可能是一個問題。但是,它會不會在從磁盤讀取實際數據和讀取數據之間產生這麼大的差異? – Lifetalk 2011-05-18 18:39:10

+0

在提示符下用'free'檢查內存使用情況。看看交換文件是否被擊中 – 2011-05-18 20:15:24

回答

0

通常,由於預取和文件系統開銷,實際I/O比用戶空間I/O更快。但是,這絕不應該鎖定您的服務器。只要緩存大小介於1Kb和16MiB之間,緩存大小就幾乎沒有影響。但是,不要使用php來傳輸文件,你應該考慮更加優化的readfile。這就是說,除非出現嚴重的編程錯誤,否則這種行爲可能與您的小循環無直接關係。首先,你應該使用iotop來找出哪個程序實際上導致了I/O。如果它是PHP(有多少併發腳本?抱歉,截圖似乎完全是亂碼,並顯示沒有有用的信息),排除你正在使用輸出緩衝,並看看內存消耗以及各種PHP調優參數(phpinfo有一個很好的概述)。順便說一句,htop是一種更好的選擇頂部;)。

+0

感謝您的建議。我知道我沒有發佈足夠的數據,但屏幕截圖來自dstat--它同時監控磁盤I/O,網絡活動,CPU信息和加載平均數。這讓我很清楚是什麼導致了什麼。 話雖如此 - 我使用iotop。根據iotop,當磁盤讀取跳轉時,他們是因爲Apache/httpd(PHP作爲apache模塊運行)。所以這絕對是。 我已經排除了輸出緩衝區,謝謝你的建議:)並且內存利用率不是我擔心的 - 通常是8GB總系統內存中的5-6GB空閒空間。 – Lifetalk 2011-05-18 18:41:07

+1

@Lifetalk截圖僅顯示系統整體。對於我們所知的一切,你可能會有另一個過程導致這種緩慢。您應該真正包含按(h)top和iotop收集的每個進程的數據。 – phihag 2011-05-18 18:45:34

3

8 * 1024 bytes?這似乎是非常合理的,如果是這樣的話,你的高磁盤I/O可能與併發請求有關。您是否考慮過實施某種帶寬限制?這裏是一個PHP的唯一實現我爲我的框架,phunction

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()第一指標得到避免強調你的CPU的值降低$speed

+0

您的框架看起來與我見過的其他人不同。您指定HMVC設計模式。我想我會在一段時間內研究你的代碼。感謝您打開它。 – daganh 2011-05-18 19:33:19

+0

@daganh:謝謝。雖然沒有文檔,但這個答案在一些(一些過時的)細節中公開了它的一部分:http://stackoverflow.com/questions/3023818/any-procedural-non-oo-php-framework/3245377#3245377。 – 2011-05-18 19:46:31

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可以使用文件塊而不是整個文件。

+0

如果他的問題與內存使用有關,但我知道這一點,但磁盤I/O? – 2011-05-18 19:15:53

+0

他的意思是說可以有超過100MB的文件的多個請求,並且如果腳本將該文件轉儲到apache,那麼它可能會快速地佔用內存並且服務器加速交換驅動器以處理內存緩衝文件。這可以快速進入看似鎖定服務器的東西。 – daganh 2011-05-18 19:31:53

+0

他對@phihag的評論回答:「內存利用率不是我所擔心的 - 通常是5-6GB免費」。 – 2011-05-18 19:44:43