图片上传存储策略

时间:2010-04-15 20:15:33

标签: php file upload storage

当用户将图像上传到我的网站时,图像会经历此过程;

  • 用户上传图片
  • 在db中存储pic元数据,为图像提供唯一ID
  • 异步图像处理(缩略图创建,裁剪等)
  • 所有图片都存储在同一个上传文件夹中

到目前为止,该网站非常小,上传目录中只有大约200,000张图片。我意识到我远远超出了目录中文件的物理限制,但这种方法显然无法扩展,所以我想知道是否有人对上传/存储策略有任何建议来处理大量的图像上传。

修改 创建用户名(或更具体地说,用户ID)子文件夹似乎是一个很好的解决方案。通过更多的挖掘,我在这里找到了一些很棒的信息; How to store images in your filesystem
但是,如果将CDN购买到等式中,这个用户ID方法是否可以很好地扩展?

7 个答案:

答案 0 :(得分:26)

之前我回答了类似的问题,但我找不到,也许OP删除了他的问题......

无论如何,Adams solution似乎是迄今为止最好的,但它不是防弹的,因为images/c/cf/(或任何其他目录/子目录对)仍然可以包含多达16 ^ 30如果我们计算图像扩展名,那么独特的哈希值和至少3倍的文件,比任何常规文件系统都能处理的要多得多。

AFAIK,SourceForge.net也将此系统用于项目存储库,例如"fatfree" project将放在projects/f/fa/fatfree/,但我相信它们将项目名称限制为8个字符。


我会将图像哈希与DATE / DATETIME / TIMESTAMP字段一起存储在数据库中,以指示图像上传/处理的时间,然后将图像放置在类似的结构中这样:

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

或者:

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

除了更具描述性之外,这种结构足以托管数十万(取决于您的文件系统限制)每天几千年的图像,这是Wordpress和其他人这样做的方式,我认为他们在这个方面做得对。

可以在数据库上轻松查询重复的图像,您只需创建符号链接。

当然,如果这对您来说还不够,您可以随时添加更多子目录(小时,分钟......)。

我个人不会使用用户ID,除非您的数据库中没有该信息,因为:

  1. 在网址中披露用户名
  2. 用户名是易变的(您可以重命名文件夹,但仍然......)
  3. 用户可以假设上传大量图片
  4. 没有目的(?)
  5. 关于CDN,我认为这个方案(或任何其他方案)没有任何理由不起作用......

答案 1 :(得分:12)

MediaWiki生成上传文件名称的MD5总和,并使用MD5的前两个字母(例如“c”和“f”的总和“cf1e66b779​​18167a6b6b972c12b1c00d”)来创建此目录结构:

images/c/cf/Whatever_filename.png

您还可以将图像ID用于每个目录的文件数量的可预测上限。也许用floor(image unique ID / 1000)确定父目录,每个目录1000个图像。

答案 2 :(得分:3)

是的,我知道这是一个古老的话题。但是存储大量图像的问题以及如何组织底层文件夹结构。所以我提出了处理它的方法,希望这可能对某些人有所帮助。

使用md5哈希的想法是处理海量图像存储的最佳方式。请记住,不同的值可能具有相同的哈希值我强烈建议在路径中添加用户ID或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

答案 3 :(得分:2)

您是否考虑过使用Amazon S3这样的东西存储文件?我经营一家照片托管公司,在我们自己的服务器上迅速达到限制后,我们切换到了AmazonS3。 S3的美妙之处在于没有像inode这样的限制,你只是不停地向它扔文件。

另外:如果您不喜欢S3,您可以随时尝试将其分解为子文件夹:

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

答案 4 :(得分:1)

您可以将用户名转换为md5,并为头像的md5转换用户名的2-3个首字母设置文件夹,并为可以转换和播放时间,随机字符串,ID和名称的图像设置文件夹

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, '-_,', '+/='));

Steam和dokuwiki使用这种结构。

答案 5 :(得分:0)

您可能会考虑开源http://danga.com/mogilefs/,因为它非常适合您正在做的事情。它会让你从考虑文件夹到命名空间(可能是用户),并让它为你存储图像。最好的部分是您不必关心数据的存储方式。它使它完全冗余,你甚至可以设置控制冗余缩略图的方式。

答案 6 :(得分:0)

我长期使用灵魂。它是相当古老的代码,可以进一步优化,但它仍然可以很好地服务。

这是一个基于以下内容创建目录结构的不可变函数:

  1. 标识图像的数字(文件ID):
  2. 建议这个数字对于基本目录是唯一的,就像数据库表的主键一样,但它不是必需的。

    1. 基本目录

    2. 所需的最大文件数和第一级子目录。只有当每个文件ID都是唯一的时,才能保留此承诺。

    3. 使用示例:

      明确使用文件ID:

      functionOne().done( functionTwo() );
      

      使用文件名,number = crc32(filename)

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

      代码:

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