当用户将图像上传到我的网站时,图像会经历此过程;
到目前为止,该网站非常小,上传目录中只有大约200,000张图片。我意识到我远远超出了目录中文件的物理限制,但这种方法显然无法扩展,所以我想知道是否有人对上传/存储策略有任何建议来处理大量的图像上传。
修改
创建用户名(或更具体地说,用户ID)子文件夹似乎是一个很好的解决方案。通过更多的挖掘,我在这里找到了一些很棒的信息; How to store images in your filesystem
但是,如果将CDN购买到等式中,这个用户ID方法是否可以很好地扩展?
答案 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,除非您的数据库中没有该信息,因为:
关于CDN,我认为这个方案(或任何其他方案)没有任何理由不起作用......
答案 1 :(得分:12)
MediaWiki生成上传文件名称的MD5总和,并使用MD5的前两个字母(例如“c”和“f”的总和“cf1e66b77918167a6b6b972c12b1c00d”)来创建此目录结构:
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的最佳解决方案。你最终会在一个忙碌的日子里看到大型的图像文件夹,而在频繁出现的图像文件夹中几乎是空的。不确定这会导致性能问题,但数据美学和一致的数据分布总是优越的。
我编写了以下函数,以便轻松生成这种基于哈希的存储路径。如果你喜欢,请随意使用它。
/**
* 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>";
答案 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)
我长期使用灵魂。它是相当古老的代码,可以进一步优化,但它仍然可以很好地服务。
这是一个基于以下内容创建目录结构的不可变函数:
建议这个数字对于基本目录是唯一的,就像数据库表的主键一样,但它不是必需的。
基本目录
所需的最大文件数和第一级子目录。只有当每个文件ID都是唯一的时,才能保留此承诺。
使用示例:
明确使用文件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;