2010-04-15 84 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; 
    
    
    } 
    
    }