Стратегии хранения загружаемых изображений


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

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

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

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

Author: Community, 2010-04-16

7 answers

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

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

АФАИК, SourceForge.net также использует эту систему для проекта репозитории, например, проект "без жира" будет размещен в 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, я не вижу причин, по которым эта схема (или любая другая) не будет работать...

 26
Author: Alix Axel, 2017-05-23 12:18:18

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

images/c/cf/Whatever_filename.png

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

 12
Author: Annika Backstrom, 2010-04-15 20:22:59

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

Идея использования хэша md5 - лучший способ обработки массивного хранилища изображений. Имея в виду, что разные значения могут иметь один и тот же хэш, я настоятельно рекомендую добавить также идентификатор пользователя или имя пользователя в путь, чтобы сделать его уникальным. Да, вот и все, что необходимый. Если у кого-то есть разные пользователи с одинаковым идентификатором базы данных - ну, что-то не так;) Так что root_path/md5_hash/user_id - это все, что вам нужно, чтобы сделать это правильно.

Использование ДАТЫ/ВРЕМЕНИ/МЕТКИ ВРЕМЕНИ, кстати, не является оптимальным решением. В итоге вы получаете большие скопления папок с изображениями в оживленный день и почти пустые в менее посещаемых. Не уверен, что это приводит к проблемам с производительностью, но есть что-то вроде эстетики данных, и согласованное распределение данных всегда лучше.

Поэтому я явно выбираю решение для хэша. 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
Author: Hexodus, 2017-04-15 19:35:31

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

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

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

 2
Author: Mitch Dempsey, 2010-04-19 02:06:59

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

8648b8f3ce06a7cc57cf6fb931c91c55 - линия разработки

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

Это будет выглядеть как

Структура:

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

 1
Author: devcline, 2014-05-20 21:06:49

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

 0
Author: Keith Adler, 2010-04-15 20:30:05

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

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

  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;


}

}
 0
Author: Tomasz Zadora, 2017-06-14 18:43:52