2010-04-15 133 views
25

當用戶上傳圖片到我的網站時,圖片會經歷這個過程;圖片上傳存儲策略

  • 用戶上傳PIC中分貝
  • 存儲PIC元數據,使圖像的唯一id
  • 異步的圖像處理(略圖創建,裁剪等)
  • 所有圖像都存儲在相同的上載的文件夾

到目前爲止,該網站非常小,上傳目錄中只有20萬張圖像。我意識到我遠沒有接近目錄中文件的物理限制,但這種方法顯然不會擴展,所以我想知道是否有人對處理大量圖像上傳的上傳/存儲策略有任何建議。

編輯: 創建用戶名(或更具體地,用戶ID)子文件夾似乎是一個很好的解決方案。隨着更多的挖掘,我發現這裏有一些很好的信息; How to store images in your filesystem
但是,如果將CDN購買到等式中,該用戶標識符方法是否可以很好地擴展?

+1

您是否考慮爲每個用戶製作一個文件夾,可能使用/ letter /用戶名格式(例如'images/o/omg_unicorns'或'images/p/powerlord') – Powerlord 2010-04-15 20:19:00

+0

工作正常,但用戶名可以更改。我將編輯並添加此信息。 – Mathew 2010-04-15 20:25:33

回答

24

我已經回答過類似的問題,但我找不到它,也許OP刪除了他的問題...

無論如何,Adams solution似乎是迄今爲止最好的,但它不是防彈的,因爲images/c/cf/(或任何其他目錄/子目錄對)仍可能包含高達16^30個獨特哈希和至少3個tim如果我們計算圖片擴展名,那麼它會有更多的文件,比任何常規文件系統都能處理的多得多但是我相信他們將項目名稱限制爲8個字符。例如,"fatfree" project將被放置在projects/f/fa/fatfree/,但我相信他們將項目名稱限制爲8個字符。

images/ 
    2010/          - Year 
    04/          - Month 
     19/         - Day 
     231c2ee287d639adda1cdb44c189ae93.png - Image Hash 


我將在圖像的哈希值在數據庫中與DATE/DATETIME/TIMESTAMP字段,其​​指示當圖像被上傳/處理,然後將圖像在這樣的結構一起存儲

或者:

images/ 
    2010/         - Year 
    0419/         - Month & Day (12 * 31 = 372) 
     231c2ee287d639adda1cdb44c189ae93.png - Image Hash 

除了是更具描述性的,這種結構也足以^h成千上萬的(取決於您的文件系統限制)每天幾千年的圖像,這是Wordpress和其他人這樣做的方式,我認爲他們在這一方面是正確的。

可以在數據庫中輕鬆查詢重複的圖像,並且只需創建符號鏈接。

當然,如果這對於你來說還不夠,你總是可以添加更多的分區(小時,分鐘......)。

我個人不會使用用戶ID,除非你沒有在數據庫中可用的信息,這是因爲:

  1. 在URL
  2. 用戶名用戶名的披露是揮發性的(你可以要重命名文件夾,但仍...)
  3. 用戶可以假設上傳大量圖片
  4. 沒有用處(?)

關於CDN,我沒有看到任何理由,這種方案(或任何其他)不會工作...

12

鏈接到MediaWiki生成上傳的文件名的MD5校驗和,並使用MD5的前兩個字母(比如,「C」和和「cf1e66b779​​18167a6b6b972c12b1c00d」的「F」)來創建此目錄結構:

images/c/cf/Whatever_filename.png 

您也可以使用圖像ID作爲每個目錄文件數量的可預測上限。可能需要floor(image unique ID/1000)來確定父目錄,每個目錄1000個圖像。

+1

我們使用類似的方法,但是採用4級深度結構: 12/34/56/78 適用於數百萬個文件。 – Evert 2010-04-19 05:48:41

+0

什麼是圖像ID?如何在PHP中找到這個? – carbonr 2012-05-05 05:32:22

+0

爲什麼不能/ c/f /? – 2014-02-02 14:09:38

0

你可能會認爲開源http://danga.com/mogilefs/,因爲它是完美的你在做什麼。它會讓你從考慮文件夾到命名空間(可能是用戶),並讓它爲你存儲圖像。最好的部分是你不必關心數據的存儲方式。它使得它完全冗餘,你甚至可以設置控制如何多餘的縮略圖。

2

你有沒有想過使用類似亞馬遜S3的東西來存儲文件?我運行一家照片託管公司,在我們自己的服務器上迅速達到限制後,我們切換到了AmazonS3。 S3的美妙之處在於沒有像inode那樣的限制,什麼不是,你只是不停地向它扔文件。

另外:如果你不喜歡S3,你總是可以嘗試把它分解成子文件夾了,您可以:

/userid/year/month/day/photoid.jpg

1

您可以將用戶名轉換爲md5,並設置一個文件夾從2-3第一個字母的MD5轉換用戶名爲頭像和你可以轉換圖像,並與時間玩,隨機字符串,編號和名稱

8648b8f3ce06a7cc57cf6fb931c91c55 - devcline

而且用戶名或ID的下一個文件夾或逆

第一個字母它看起來像

結構:

stream/img/86/8b8f3ce06a7cc57cf6fb931c91c55.png //simplest 
stream/img/d/2/0bbb630d63262dd66d2fdde8661a410075.png //first letter and id folders 
stream/img/864/d/8b8f3ce06a7cc57cf6fb931c91c55.png // with first letter of the nick 
stream/img/864/2/8b8f3ce06a7cc57cf6fb931c91c55.png //with unique id 
stream/img/2864/8b8f3ce06a7cc57cf6fb931c91c55.png //with unique id in 3 letters 
stream/img/864/2_8b8f3ce06a7cc57cf6fb931c91c55.png //with unique id in picture name 

代碼

$username = substr($username_md5, 1); // to cut first letter from the md5 converted nick 
$username_first = $username[0]; // the first letter 
$username_md5 = md5($username); // md5 for username 
$randomname = uniqid($userid).md5(time()); //for generate a random name based on ID 

您可以使用Base64

$image_encode = strtr(base64_encode($imagename), '+/=', '-_,'); 
$image_decode = base64_decode(strtr($imagename, '-_,', '+/=')); 

蒸汽和DokuWiki的使用這種結構也試試。

2

是的,是的,我知道這是一個古老的話題。但是存儲大量圖像的問題以及底層文件夾結構應如何組織。所以我以我的方式來處理它,希望這可以幫助一些人。

使用md5散列的想法是處理海量圖像存儲的最佳方式。請記住,不同的值可能具有相同的散列,我強烈建議將用戶標識或nicname添加到路徑中以使其具有唯一性。是的,這就是所需要的。如果某人有不同的用戶使用相同的數據庫ID - 那麼出現問題了;)因此root_path/md5_hash/user_id就是您需要做的所有事情。

使用DATE/DATETIME/TIMESTAMP不是IMO方式的最佳解決方案。你最終會在布西日獲得大量的圖片文件夾,而在不太經常光顧的圖片文件夾中幾乎是空的。不確定這會導致性能問題,但有一些像數據美學和一致的數據分佈總是優越的。

所以我清楚地去尋找解決方案。 enter image description here

我寫了下面的函數,以便於生成這種基於散列的存儲路徑。隨意使用它,如果你喜歡它。

/** 
* Generates directory path using $user_id md5 hash for massive image storing 
* @author Hexodus 
* @param string $user_id numeric user id 
* @param string $user_root_raw root directory string 
* @return null|string 
*/ 

function getUserImagePath($user_id = null, $user_root_raw = "images/users", $padding_length = 16, 
          $split_length = 3, $hash_length = 12, $hide_leftover = true) 
{ 
    // our db user_id should be nummeric 
    if (!is_numeric($user_id)) 
     return null; 

    // clean trailing slashes 
    $user_root_rtrim = rtrim($user_root_raw, '/\\'); 
    $user_root_ltrim = ltrim($user_root_rtrim, '/\\'); 
    $user_root = $user_root_ltrim; 

    $user_id_padded = str_pad($user_id, $padding_length, "0", STR_PAD_LEFT); //pad it with zeros 
    $user_hash = md5($user_id); // build md5 hash 

    $user_hash_partial = $hash_length >=1 && $hash_length < 32 
         ? substr($user_hash, 0, $hash_length) : $user_hash; 
    $user_hash_leftover = $user_hash_partial <= 32 ? substr($user_hash, $hash_length, 32) : null; 

    $user_hash_splitted = str_split($user_hash_partial, $split_length); //split in chunks 
    $user_hash_imploded = implode($user_hash_splitted,"/"); //glue aray chunks with slashes 

    if ($hide_leftover || !$user_hash_leftover) 
     $user_image_path = "{$user_root}/{$user_hash_imploded}/{$user_id_padded}"; //build final path 
    else 
     $user_image_path = "{$user_root}/{$user_hash_imploded}/{$user_hash_leftover}/{$user_id_padded}"; //build final path plus leftover 

    return $user_image_path; 
} 

功能測試呼叫:

$user_id = "1394"; 
$user_root = "images/users"; 
$user_hash = md5($user_id); 
$path_sample_basic = getUserImagePath($user_id); 
$path_sample_advanced = getUserImagePath($user_id, "images/users", 8, 4, 12, false); 

echo "<pre>hash: {$user_hash}</pre>"; 
echo "<pre>basic:<br>{$path_sample_basic}</pre>"; 
echo "<pre>customized:<br>{$path_sample_advanced}</pre>"; 
echo "<br><br>"; 

輸出結果 - 着色爲了您的方便): enter image description here

+1

很好的答案..絕對幫助我理解散列存儲最好的。雖然用戶/有點長之後不是你的分區散列嗎?如果它是4個十六進制長(如f016),這是不是意味着可能存儲15 * 15 * 15 * 15(50625)文件夾?如果它是2十六進制長(f0),最大文件夾將是15 * 15(256)?這不是更可取嗎?在原始圖像中,您將md5散列分區爲長度爲4 hex的8個不同目錄。這不是太過於誇張,並且瀏覽這麼多子文件夾會影響性能嗎? – user3614030 2017-10-22 21:37:25

+1

@ user3614030我很高興我的回答對你有幫助。正如你所看到的,我還使用了一個通常是來自數據庫的唯一ID的ID,所以哈希的全長不是必需的。如果子文件夾對性能有影響,我誠實不知道。 – Hexodus 2017-10-24 16:37:24

0

我使用很長一段時間了soultion IM。這是非常古老的代碼,可以進一步優化,但它仍然很好。

這是一個不可變的函數創建目錄結構基於:

  1. 編號識別圖像(FILE ID):

它的建議,這NUMER爲基目錄獨特,像主鍵數據庫表,但它不是必需的。

  • 的基本目錄

  • 文件和第一級子目錄的最大期望數目。這個承諾只有在每個FILE ID都是唯一的時候才能保留。使用的

  • 實施例:

    使用明確文件ID:

    $fileName = 'my_image_05464hdfgf.jpg'; 
    $fileId = 65347; 
    $baseDir = '/home/my_site/www/images/'; 
    $baseURL = 'http://my_site.com/images/'; 
    
    $clusteredDir = \DirCluster::getClusterDir($fileId); 
    $targetDir = $baseDir . $clusteredDir; 
    $targetPath = $targetDir . $fileName; 
    $targetURL = $baseURL . $clusteredDir . $fileName; 
    

    使用文件名稱,編號= CRC32(文件名)

    $fileName = 'my_image_05464hdfgf.jpg'; 
    $baseDir = '/home/my_site/www/images/'; 
    $baseURL = 'http://my_site.com/images/'; 
    
    $clusteredDir = \DirCluster::getClusterDir($fileName); 
    $targetDir = $baseDir . $clusteredDir; 
    $targetURL = $baseURL . $clusteredDir . $fileName; 
    

    代碼:

    class DirCluster { 
    
    
    /** 
    * @param mixed $fileId  - numeric FILE ID or file name 
    * @param int $maxFiles  - max files in one dir 
    * @param int $maxDirs  - max 1st lvl subdirs in one dir 
    * @param boolean $createDirs - create dirs? 
    * @param string $path  - base path used when creatign dirs 
    * @return boolean|string 
    */ 
    public static function getClusterDir($fileId, $maxFiles = 100, $maxDirs = 10, 
    $createDirs = false, $path = "") { 
    
    // Value for return 
    $rt = ''; 
    
    // If $fileId is not numerci - lets create crc32 
    if (!is_numeric($fileId)) { 
        $fileId = crc32($fileId); 
    } 
    
    if ($fileId < 0) { 
        $fileId = abs($fileId); 
    } 
    
    if ($createDirs) { 
    
        if (!file_exists($path)) 
        { 
         // Check out the rights - 0775 may be not the best for you 
         if (!mkdir($path, 0775)) { 
          return false; 
         } 
         @chmod($path, 0775); 
        } 
    } 
    
    if ($fileId <= 0 || $fileId <= $maxFiles) { 
        return $rt; 
    } 
    
    // Rest from dividing 
    $restId = $fileId%$maxFiles; 
    
    $formattedFileId = $fileId - $restId; 
    
    // How many directories is needed to place file 
    $howMuchDirs = $formattedFileId/$maxFiles; 
    
    while ($howMuchDirs > $maxDirs) 
    { 
        $r = $howMuchDirs%$maxDirs; 
        $howMuchDirs -= $r; 
        $howMuchDirs = $howMuchDirs/$maxDirs; 
        $rt .= $r . '/'; // DIRECTORY_SEPARATOR =/
    
        if ($createDirs) 
        { 
         $prt = $path.$rt; 
         if (!file_exists($prt)) 
         { 
          mkdir($prt); 
          @chmod($prt, 0775); 
         } 
        } 
    } 
    
    $rt .= $howMuchDirs-1; 
    if ($createDirs) 
    { 
        $prt = $path.$rt; 
        if (!file_exists($prt)) 
        { 
         mkdir($prt); 
         @chmod($prt, 0775); 
        } 
    } 
    
    $rt .= '/'; // DIRECTORY_SEPARATOR 
    
    return $rt; 
    
    
    } 
    
    }