Стратегии хранения изображений для загрузки

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

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

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

РЕДАКТИРОВАТЬ: Создание вложенных папок имени пользователя (или, точнее, идентификатора пользователя) могло бы быть хорошим решением. Немного покопавшись, я нашел здесь отличную информацию; Как хранить изображения в вашей файловой системе
Однако будет ли это Подход userid dir хорошо масштабируется, если в уравнение входит CDN?


person Mathew    schedule 15.04.2010    source источник
comment
Рассматривали ли вы создание папки для каждого пользователя, возможно, используя формат / letter / username (например, images/o/omg_unicorns или images/p/powerlord)   -  person Powerlord    schedule 16.04.2010
comment
Это работает, но имена пользователей могут измениться. Я отредактирую и добавлю эту информацию.   -  person Mathew    schedule 16.04.2010


Ответы (7)


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

В любом случае, решение Адамса пока что кажется лучшим, но это не так. t пуленепробиваемый, поскольку images/c/cf/ (или любая другая пара каталог / подкаталог) все еще может содержать до 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

Or:

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

Помимо большей наглядности, этой структуры достаточно для размещения сотен тысяч (в зависимости от ограничений вашей файловой системы) изображений в день в течение нескольких тысяч лет. Именно так Wordpress и другие делают это, и я думаю, они правильно поняли это.

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

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

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

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

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

person Alix Axel    schedule 19.04.2010

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

images/c/cf/Whatever_filename.png

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

person Annika Backstrom    schedule 15.04.2010
comment
Мы используем аналогичный подход, но с четырехуровневой глубокой структурой: 12/34/56/78 Отлично работает для миллионов файлов. - person Evert; 19.04.2010
comment
какой идентификатор изображения? Как это найти в PHP? - person carbonr; 05.05.2012
comment
+ Пользователь Не стесняйтесь красить навес для велосипеда в любой цвет, который вам нравится. - person Annika Backstrom; 04.02.2014

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

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

Полученный результат - раскрашен для вашего удобства;):  введите описание изображения здесь

person Hexodus    schedule 15.04.2017
comment
Хороший ответ ... определенно помог мне лучше понять хеш-хранилище. Хотя разве ваш разделенный хеш после пользователей / не слишком длинный? Если это 4 шестнадцатеричных файла (например, f016), не означает ли это, что потенциально там могут храниться 15 * 15 * 15 * 15 (50625) папок? Если это 2 шестнадцатеричных длины (f0), максимальное количество папок будет 15 * 15 (256)? Разве это не было бы более желанным? В исходном образе вы разделили хэш md5 на 8 различных каталогов длиной 4 гексагона. Разве это не чрезмерно, и влияет ли навигация по такому количеству вложенных папок на производительность? - person user3614030; 23.10.2017
comment
@ user3614030 Я рад, что мой ответ был вам полезен. Как видите, я также использую идентификатор, который обычно является уникальным идентификатором из базы данных, поэтому полная длина хэша не требуется. Честно говоря, я понятия не имею, влияют ли вложенные папки на производительность. - person Hexodus; 24.10.2017

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

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

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

person Mitch Dempsey    schedule 19.04.2010

Вы можете преобразовать имя пользователя в 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 и dokuwiki используют эту структуру.

person warfish    schedule 20.05.2014

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

person Keith Adler    schedule 15.04.2010

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

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

  1. Номер, идентифицирующий изображение (ИДЕНТИФИКАТОР ФАЙЛА):

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

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

  2. Максимальное желаемое количество файлов и подкаталогов первого уровня. Это обещание может быть выполнено только в том случае, если каждый ИДЕНТИФИКАТОР ФАЙЛА уникален.

Пример использования:

Использование явного ИДЕНТИФИКАЦИИ ФАЙЛА:

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


}

}
person Tomasz Zadora    schedule 14.06.2017