图片上传存储策略

Posted

技术标签:

【中文标题】图片上传存储策略【英文标题】:Image upload storage strategies 【发布时间】:2011-02-08 13:43:14 【问题描述】:

当用户上传图片到我的网站时,图片会经过这个过程;

用户上传图片 将图片元数据存储在数据库中,为图片赋予唯一的ID 异步图像处理(缩略图创建、裁剪等) 所有图片都存储在同一个上传文件夹中

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

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

【问题讨论】:

您是否考虑过为每个用户创建一个文件夹,可能使用 /letter/username 格式(例如 images/o/omg_unicornsimages/p/powerlord 可行,但用户名可以更改。我将编辑并添加此信息。 【参考方案1】:

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

images/c/cf/Whatever_filename.png

您还可以使用图像 ID 来确定每个目录的文件数上限。也许用floor(image unique ID / 1000) 来确定父目录,每个目录有 1000 张图像。

【讨论】:

我们使用类似的方法,但采用 4 级深度结构:12/34/56/78 适用于数百万个文件。 图片ID是什么?如何在 php 中找到这个? +用户随意为自行车涂上你喜欢的任何颜色。【参考方案2】:

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

【讨论】:

【参考方案3】:

您是否考虑过使用 Amazon S3 之类的东西来存储文件?我经营一家照片托管公司,在我们自己的服务器上很快达到限制后,我们切换到了 AmazonS3。 S3 的美妙之处在于没有 inode 之类的限制,您只需不断向其扔文件即可。

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

/userid/年/月/日/photoid.jpg

【讨论】:

【参考方案4】:

我之前回答过一个类似的问题,但我找不到它,也许 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,除非您的数据库中没有该信息,因为:

    在 URL 中披露用户名 用户名不稳定(您也许可以重命名文件夹,但仍然...) 假设用户可以上传大量图片 毫无用处 (?)

关于 CDN,我看不出这个方案(或任何其他方案)不起作用的任何原因......

【讨论】:

【参考方案5】:

您可以将用户名转换为 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 使用这种结构。

【讨论】:

【参考方案6】:

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

使用 md5 哈希的想法是处理海量图像存储的最佳方式。请记住,不同的值可能具有相同的哈希值,我强烈建议将用户 ID 或 nicname 也添加到路径中以使其唯一。是的,这就是我们所需要的。如果某人有不同的用户具有相同的数据库 ID - 那么,有问题;)所以root_path/md5_hash/user_id 是你需要做的一切。

顺便说一句,使用 DATE / DATETIME / TIMESTAMP 并不是最佳解决方案。在忙碌的一天,你会得到一大堆图像文件夹,而在不常去的时候,这些文件夹几乎是空的。不确定这会导致性能问题,但存在数据美学之类的问题,并且一致的数据分布始终是优越的。

所以我显然选择了哈希解决方案。

我编写了以下函数来轻松生成此类基于散列的存储路径。如果您喜欢,请随意使用它。

/**
* 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>";

生成的输出 - 为了您的方便而着色;):

【讨论】:

很好的答案.. 绝对帮助我最好地理解散列存储。虽然你的分区散列在 users/ 之后不是有点长吗?如果它是 4 十六进制长(如 f016),这是否意味着可能有 15*15*15*15 (50625) 个文件夹可以存储在那里?如果它是 2 十六进制长 (f0),最大文件夹将是 15*15 (256)?这不是更可取吗?在您的原始图像中,您将 md5 哈希划分为 8 个长度为 4 十六进制的不同目录。这不是太过分了,浏览这么多子文件夹会影响性能吗? @user3614030 很高兴我的回答对您有所帮助。如您所见,我还使用了通常是数据库中唯一 ID 的 ID,因此不需要完整长度的哈希。老实说,我不知道子文件夹是否会影响性能。【参考方案7】:

我已经使用了很长时间的灵魂。这是相当老的代码,可以进一步优化,但它仍然可以很好地发挥作用。

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

    标识图像的编号(文件 ID):

建议此数字对于基目录是唯一的,如数据库表的主键,但不是必需的。

    基本目录

    文件和第一级子目录的最大期望数量。这个承诺只有在每个 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;





【讨论】:

以上是关于图片上传存储策略的主要内容,如果未能解决你的问题,请参考以下文章

php表单上传图片到七牛云存储并返回地址……求具体流程~有代码更好

图片上传Security Error

thinkphp如何将多张上传的图片根据状态分别存储!

ckeditor无法上传图片

使用 amazon sdk 和存储桶策略从 ios 应用程序将图像上传到 amazon s3

上传图片,通过node服务器存储在指定目录