Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу).
Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Homepage Карта сайта Версия для печати

Джентльменский набор Web-разработчика   Ларри Уолл о Perl6   Наблы Система Orphus
 

48. Правильный способ кэширования данных

[20 апреля 2008 г.] обсудить статью в форуме

В предыдущей набле мы говорили о технологии тэгирования кэша и о том, как ее применять совместно с Zend Framework и backend-частью библиотеки Dklab_Cache. Сегодня речь пойдет о том, как сделать код кэширования менее "замусоренным" лишними зависимостями.

Что это за зависимости? А давайте посмотрим. В типичном случае нам приходится несколько раз ссылаться за ключ кэширования, чтобы эффективно работать с данными:

Листинг 1: Типичное использование кэша
if (false === ($data = $cache->load("profile_{$userId}"))) {
    $data = loadProfileOf($userId);
    $cache->save($data, "profile_{$userId}", array(), 3600 * 24); // кэширование на 24 часа
}
display($data);

и потом еще в совершенно другой части программы:

Листинг 2: Типичная очистка кэша
$cache->remove("profile_{$userId}");

Именно так работает кэш в facebook.com: ручная проверка и запись кэш-ключа соседствует с ручной же его очисткой.

Как видите, фразу "profile_{$userId}" приходится повторять аж три раза. И если в первом случае мы можем убрать повтор ценой введения новой переменной:

Листинг 3: Введение временной переменной для ключа кэша
$cacheKey = "profile_{$userId}";
$cacheTime = Config::getInstance()->cacheTime->profile;
if (false === ($data = $cache->load($cacheKey))) {
    $data = loadProfileFor($userId);
    $cache->save($data, $cacheKey, array(), $cacheTime);
}
display($data);

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

Лирическое отступление 
Строчка "profile_{$userId}" — это именно знание, и не следует недооценивать вред о распространении этого знания по излишне большому числу независимых мест. В нашем примере знание очень просто, но на практике ключ кэша может зависеть от десятков различных параметров, часть из которых нужно даже загружать из БД по первому требованию.

Ситуация в действительности даже хуже, чем может показаться.

  • Кто может дать гарантию, что в переменной $userId хранится именно ID текущего пользователя, а не какой-нибудь мусор? А что, если кто-то попробует подставить туда неверные данные? Очевидно, что ключ кэша в действительности зависит не от ID пользователя, а от самого этого пользователя. Попытка использовать для генерации ключа что-либо, кроме объекта-пользователя, заведомо ошибочна, но в программе это ограничение явно не выражено.
  • Время кэширования мы должны хранить не прямо в коде, а где-то в конфигурации системы (см. предыдущий пример), чтобы его можно было менять, не трогая код. Это — еще одна зависимость от роли кэш-ячейки и строчки "profile".

Dklab_Cache_Frontend_Slot: решение проблемы зависимостей

Вместо долгих разъяснений я сразу приведу пример использования Slot-класса, построенного в соответствии с идеологией Dklab_Cache_Frontend.

Листинг 4
$slot = new Cache_Slot_UserProfile($user);
if (false === ($data = $slot->load())) {
    $data = $user->loadProfile();
    $slot->save($data);
}
display($data);

Для очистки кэша:

Листинг 5
$slot = new Cache_Slot_UserProfile($user);
$slot->remove();

Все знание о механике построения ключа кэша содержится в классе Cache_Slot_UserProfile, который можно определить так:

Листинг 6: Определение класса-слота
class Cache_Slot_UserProfile extends Cache_Slot_Abstract {
    public function __construct(User $user) {
        parent::__construct("profile_{$user->id}", 3600 * 24);
    }
}

Чем же это лучше?

  • Знание об алгоритме построения ключа кэша заключено в едином месте — в классе Cache_Slot_UserProfile.
  • Там же заключено знание о времени жизни кэша. В нашем случае мы задали его явно, однако никто не мешает брать время жизни из параметра конфигурации, имя которого совпадает с именем слот-класса.
  • Параметр $user конструктора класса Cache_Slot_UserProfile — типизированный. Это означает, что мы не сможем "подсунуть" слот-классу что-либо, кроме корректного объекта-польователя. Естественно, зависимость может быть от нескольких объектов; все это определяется параметрами конструктора.

Вы должны написать столько собственных слот-классов, сколько видов кэш-хранилищ существует у вас в программе. Это дисциплинирует: заглянув в директорию Cache/Slot, вы сразу сможете увидеть, сколько именно различных кэшей используется в программе, а также — от чего они зависят.

Сквозное кэширование (thru-caching)

На самом деле, в ряде случаев работу с кэшем можно еще более упростить. Идею метода предложил Владимир Колесников, а выглядит это так:

Листинг 7
$slot = new Cache_Slot_UserProfile($user);
$data = $slot->thru($user)->loadProfile();
display($data);

Сказав в программе $slot->thru($user)->loadProfile(), вы в действительности даете команду запустить $user->loadProfile() в случае, если кэш пуст, или выдать данные из кэша, если он не пуст. Это довольно очевидно само по себе, а также очень сокращает письмо.

Dklab_Cache_Frontend_Tag: тэгирование слотов

Слоты, помимо прочего, поддерживают тэгирование. Вот пример использования тэгов для сквозного кэширования (естественно, можно применять и "несквозное").

Листинг 8
$slot = new Cache_Slot_UserProfile($user);
$slot->addTag(new Cache_Tag_User($loggedUser);
$slot->addTag(new Cache_Tag_Language($currentLanguage);
$data = $slot->thru($user)->loadProfile();
display($data);

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

Листинг 9: Определение класса-тэга
class Cache_Tag_User extends Cache_Tag_Abstract {
    public function __construct(User $user) {
        parent::__construct("user_{$user->id}");
    }
}

class Cache_Tag_Language extends Cache_Tag_Abstract {
    public function __construct(Language $language) {
        parent::__construct("lang_{$language->id}");
    }
}

Классы-тэги особенно удобны, когда приходит пора очищать некоторые тэги:

Листинг 10: Очистка тэгов
$tag = new Cache_Tag_Language($currentLanguage);
$tag->clean();

Как видите, знание о зависимостях тэгов снова хранится в единственном месте. Вы теперь просто не сможете случайно "промахнуться" и очистить не тот тэг: система выдаст ошибку либо о несуществующем классе, либ о неверном типе параметра конструктора.

А где же библиотека?

А библиотека — в разделе Конструктор: Поддержка со стороны frontend: Dklab_Cache_Frontend. Там же вы сможете найти техническую информацию по внедрению библиотеки и другие примеры ее использования.

обсудить статью в форуме

 
Рекламный блок
   

На странице:
    48. Правильный способ кэширования данных
Dklab_Cache_Frontend_Slot: решение проблемы зависимостей
Сквозное кэширование (thru-caching)
Dklab_Cache_Frontend_Tag: тэгирование слотов
А где же библиотека?

Важное объявление:
    автор категорически против копирования и распространения в Интернете всех статей «Куроводства» с возрастом, меньшим 6 месяцев. Печальный опыт «расползания» чрезвычайно устаревших ошибочных версий статьи про Apache действительно объясняет такое решение.

Орфография на «Куроводстве»:
    если вы заметили орфографическую, стилистическую или другую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Выделенный текст будет немедленно отослан вебмастеру, а Вы даже ничего и не заметите — настолько быстро все произойдет.

На заметку:
    если вы уже вскипели насчет дизайна этой страницы, то присмотритесь повнимательнее к названию, почитайте FAQ, сходите по лебедевским местам, как это уже предлагалось выше. Можно ли считать пародию плагиатом? Надеюсь, что нет.

Параметры этой страницы
   
GZip

Ссылки от спонсоров
   


Дмитрий Котеров | 20 апреля 2008 г. ©1999-2017 | Генеральный спонсор: Хостинг «Джино» | Контакт Вернуться к оглавлению