Стратегии хранения загрузки изображений - PullRequest
29 голосов
/ 16 апреля 2010

Когда пользователь загружает изображение на мой сайт, изображение проходит этот процесс;

  • Пользователь загружает фото
  • хранить метаданные pic в дБ, придавая изображению уникальный идентификатор
  • асинхронная обработка изображений (создание миниатюр, обрезка и т. Д.)
  • все изображения хранятся в одной папке загрузок

Пока что сайт довольно маленький, и в каталоге загрузок всего ~ 200 000 изображений. Я понимаю, что физический предел количества файлов в каталоге далеко не такой, но этот подход явно не масштабируется, поэтому мне было интересно, есть ли у кого-нибудь совет по стратегиям загрузки / хранения для обработки больших объемов изображений.

EDIT: Создание подпапок username (или, более конкретно, userid) может показаться хорошим решением. Еще немного покопавшись, я нашел здесь отличную информацию; Как хранить изображения в вашей файловой системе
Тем не менее, будет ли этот подход userid dir хорошо масштабироваться, если в уравнение будет куплен CDN?

Ответы [ 7 ]

26 голосов
/ 19 апреля 2010

Я уже отвечал на аналогичный вопрос раньше, но не могу его найти, возможно, ОП удалил его вопрос ...

В любом случае, Решение Адамса кажется лучшим, но оно не пуленепробиваемое, поскольку images/c/cf/ (или любая другая пара dir / subdir) может содержать до 16 ^ 30 уникальных хешей и по крайней мере в 3 раза больше файлов, если мы посчитаем расширения изображений, намного больше, чем любая обычная файловая система может обработать.

AFAIK, SourceForge.net также использует эту систему для репозиториев проектов, например, «fatfree» проект будет размещен в 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 и другие делают это, и я думаю, что они правильно поняли это.

Дублированные изображения можно легко запросить в базе данных, и вам просто нужно создать символические ссылки.

Конечно, если вам этого недостаточно, вы всегда можете добавить дополнительные подкаталоги (часы, минуты, ...).

Лично я бы не использовал идентификаторы пользователей, если у вас нет этой информации в вашей базе данных, потому что:

  1. Раскрытие имен пользователей в URL
  2. Имена пользователей изменчивы (вы можете переименовывать папки, но все же ...)
  3. Пользователь может гипотетически загрузить большое количество изображений
  4. Не служит цели (?)

Что касается CDN, я не вижу причин, по которым эта схема (или любая другая) не будет работать ...

12 голосов
/ 16 апреля 2010

MediaWiki генерирует сумму MD5 имени загруженного файла и использует первые две буквы MD5 (скажем, «c» и «f» суммы «cf1e66b77918167a6b6b972c12b1c00d») для создания этой структуры каталога: *

images/c/cf/Whatever_filename.png

Вы также можете использовать идентификатор изображения для предсказуемого верхнего предела количества файлов в каталоге. Возможно, потребуется floor(image unique ID / 1000), чтобы определить родительский каталог, для 1000 изображений на каталог.

3 голосов
/ 15 апреля 2017

Да, да, я знаю, что это древняя тема. Но проблема в хранении большого количества изображений и в том, как должна быть организована основная структура папок. Поэтому я представляю свой способ справиться с этим в надежде, что это может помочь некоторым людям.

Идея использования хеша md5 - лучший способ хранения больших массивов изображений. Помня о том, что разные значения могут иметь один и тот же хеш, я настоятельно рекомендую добавить к пути также идентификатор пользователя или ник, чтобы сделать его уникальным. Да, это все, что нужно. Если у кого-то есть разные пользователи с одним и тем же идентификатором базы данных - хорошо, что-то не так;) Так что 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;
}

Функциональные тестовые вызовы:

<code>$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}
"; echo "
basic:<br>{$path_sample_basic}
"; echo "
customized:<br>{$path_sample_advanced}
"; эхо "

";

Полученный вывод - раскрашен для вашего удобства;): enter image description here

2 голосов
/ 19 апреля 2010

Задумывались ли вы об использовании чего-то вроде Amazon S3 для хранения файлов? Я управляю компанией, предоставляющей фотохостинг, и после быстрого достижения ограничений на нашем собственном сервере мы переключились на AmazonS3. Прелесть S3 в том, что нет никаких ограничений, таких как inode, а что нет, вы просто бросаете туда файлы.

Также: если вам не нравится S3, вы всегда можете попробовать разбить его на подпапки настолько, насколько сможете:

/ идентификатор пользователя / год / месяц / день / photoid.jpg

1 голос
/ 21 мая 2014

Вы можете преобразовать имя пользователя в md5 и установить папку из 2-3 первых букв преобразованного имени пользователя md5 для аватаров и изображений, которые вы можете конвертировать и воспроизводить со временем, случайными строками, идентификаторами и именами

8648b8f3ce06a7cc57cf6fb931c91c55 - devcline

Также первая буква имени пользователя или идентификатора для следующей папки или обратная

Будет выглядеть

Состав:

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 И докувики используют эту структуру.

0 голосов
/ 14 июня 2017

У меня есть душа, которой я пользуюсь долгое время. Это довольно старый код, и его можно дополнительно оптимизировать, но он по-прежнему хорошо работает.

Это неизменяемая функция, создающая структуру каталогов на основе:

  1. Номер, который идентифицирует изображение (ИД ФАЙЛА):

рекомендуется, чтобы этот номер был уникальным для базового каталога, как первичный ключ для таблицы базы данных, но это не обязательно.

  1. Базовый каталог

  2. Максимально необходимое количество файлов и подкаталогов первого уровня. Это обещание может быть выполнено, только если каждый 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;


}

}
0 голосов
/ 16 апреля 2010

Вы можете рассмотреть открытый код http://danga.com/mogilefs/, так как он идеально подходит для того, что вы делаете. Вам не нужно думать о папках в пространствах имен (которые могут быть пользователями) и позволять хранить изображения для вас. Самое приятное, что вам не нужно заботиться о том, как хранятся данные. Это делает его полностью избыточным, и вы даже можете установить, насколько избыточны миниатюры.

...