Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

images/c/cf/Whatever_filename.png

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

  • Номер, идентифицирующий изображение (FILE ID):

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

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

  2. Максимальное количество файлов и поддиректории первого уровня. Это обещание может быть сохранено только в том случае, если каждый идентификатор FILE уникален.

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

Использование явно FILE 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;

Использование имени файла, number = 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;


}

}