Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу).
Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). 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

Ссылки от спонсоров
    Аренда автомобиля Golf без водителя на http://baer-auto.ru.


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