Главная  /  Правка  /  История  /   /  Область пользователей

Усложненное кэширование

Введение

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

У каждого закэшированного объекта есть следующие свойства:

  1. Уникальный ключ, который его идентифицирует.
  2. Время жизни элемента, которое может быть бесконечным.
  3. Набор триггеров, которые могут инвалидировать кэш.

Например, у нас есть следующий набор триггеров:

Триггеры

Т.о. мы можем инвалидировать несколько ключей "воскрешением" различных триггеров.

Например, когда мы обновляем статью о drugs мы "воскрешаем" триггер new_drugs и инвалидируем health-новости и news_all, зависящие от данного триггера.

С другой стороны, новости "technology" остались нетронутыми.

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

Управление триггерами

Прямая обработка

При кэшировании страницы Вы можете легко добавить к ней триггер, используя функцию cppcms::cache_interface::add_trigger().

Например:

if(cache().fetch_page("my_page"))
  return;

// Создаем некоторый контент

cache().add_trigger("my_trigger");
cache().store_page("my_page");

Теперь объект my_page будет зависеть от двух триггеров my_trigger и собственного my_page.

С очисткой кэша, "воскрешением" триггера:

cache().rise("my_trigger");

будет создан кэш для всех зависящих от него объектов, включая my_page.

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

std::set<std::string> my_triggers;
my_triggers.insert("my_trigger");

cache().store_data("my_object",my_object,my_triggers);

Замечание: триггер my_object будет автоматически добавлен к ключу my_object.

Автоматическое Отслеживание Зависимостей

Кэш CppCMS позволяет автоматически отслеживать зависимости.

Предположим, есть следующий код:

if(cache().fetch_page("foo"))
  return;

if(!cache().fetch_data("settings",settings)) {
  load_settings(settings);
  cache().store_data("settings",settings);
}

// делаем что-нибудь

cache().store_page("foo");

Теперь мы используем объект "setting" для создания страницы "foo". При вызове fetch_data() или store_data() триггер "settings" добавляется как зависимый от "foo".

Более того, если у "settings" есть собственные триггеры:

if(!cache().fetch_data("settings",settings)) {
  load_settings(settings);
  std::set<std::string> settings_triggers;
  settings_triggers.insert("bar");
  cache().store_data("settings",settings);
}

, то страница "foo" будет автоматически зависеть от триггеров "settings" и "bar" и т.о., например, "воксрешение" "bar" инвалидирует и "foo" и "settings".

Автоматическая запись может быть отключена установкой параметра no_triggers в "true" для store_data() и "fetch_data()", например:

if(!cache().fetch_data("settings",settings,true)) {
  load_settings(settings);
  cache().store_data("settings",settings,-1,true);
  // -1 для бесконечного тайм-аута
}

Отслеживание вложенностей

Триггеры - регистратор (recorder) для фреймов, но иногда требуется регистрировать события триггеров на уровнях ниже. Например:

Типа:

if(cache().fetch_page("foo"))
  return;

if(!cache().fetch_data("settings",settings)) {
  if(!cache().fetch_data("local_settings",ls) {
    ...
  }
  if(!cache().fetch_data("global_settings",gs) {
    ...
  }
  load_settings(settings,ls,gs)
  cache().store_data("settings",settings);
}

cache().store_page("foo");

Но триггеры local_settings и global_settings не будут регистрировать события для "settings", потому что функция "store_data()" не знает о них.

Потому, для обработки такой ситуации есть объект triggers_recorder:

Изменим наш код следующим образом:

if(!cache().fetch_data("settings",settings)) {
  cppcms::triggers_recorder rec(cache());
  if(!cache().fetch_data("local_settings",ls) {
    ...
  }
  if(!cache().fetch_data("global_settings",gs) {
    ...
  }
  load_settings(settings,ls,gs);
  cache().store_data("settings",settings,rec.detach());
}

Запускаем экземпляр triggers_recorder и в конце раздела регистрируем события триггеров на их отсоединение вызовом метода detach(), возвращающего набор триггеров, в данном случае local_settings и global_settings.

Мы получили все, что требовалось.

Запись Текстовых Фреймов

Из Приложения

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

CppCMS предоставляет простой copy_filter.

Он используется следующим образом:

std::string frame;
if(cache().fetch_frame("key",frame)) {
  response().out() << frame;
}
else {
  cppcms::copy_filter tee(response().out());
  ...
  // генерируем что-то тяжелое
  ...
  cache().store_frame("key",tee.detach());
}

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

При вызове tee.detach() мы удалим фильтр и вернем весь записанный фрейм.

Из Представления (view)

Гораздо более естественно кэшировать некоторые фреймы из представлений (view) - движком темплейтов. CppCMS предоставляет такой инструмент: тэг <% cache ... %>.

Например:

<% template foo() %>
<html>
...
<body>

  <% cache "sidebar" %>
     <div id="sidebar">
     ...
     </div>
  <% end cache %>
  ...
</body>
</html>
<% end template>

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

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

<% cache "sidebar" on miss update_content() %>
  <div id="sidebar">
  ...
  </div>
<% end cache %>

Т.о. мы добавили контент, который рендерит callback update_content(), который может выглядеть как:

my_content : public cppcms::base_content {
 booster::function<void()> update_content;
};

И в приложении определим его как:

my_content c;
// соединяем метод `update_content` и callback
c.update_content =  std::bind(&my_app::update_content,this,&c);

И наша функция обновления будет выглядеть так:

void update_content(my_content *c)
{
   c->some_data = fetch_some_bid_data_from_db();
}

Т.о. при промахе кэша будет вызван update_content и настройка всего контента для рендеринга этого фрейма.

Замечание о кэшировании на уровне страницы

Кэширование на уровне страницы - лучшее кэширование, которое может быть сделано, если Вы всегда можете кэшировать всю страницу, а не, например, объекты базы данных, которые рендерятся в представлении (view).

Для этого есть две причины:

  1. Сериализация и десериализация объектов занимает много времени. Даже простой рендеринг отнимает время. Потому кэширование всей страницы даст лучшую производительность.
  2. Страницы CppCMS кэшируются уже с gzip-сжатием. Потому Вы не только экономите время, необходимое для сжатия страниц, но и уменьшаете время отклика, из-за данных, отправляемых гораздо меньшими, чем оригинальный HTML.

    Спасибо тому факту, что все современные браузеры поддерживают gzip-сжатие, делая кэширование на уровне страницы быстрым и эффективным.

Триггеры или Тайм-ауты

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

Если какая-то страница обновляется и посещается очень часто, то возможно, стоит установить небольшой тайм-аут и никогда не инвалидировать ее вручную.

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

Например, система кэширования блога CppCMS полностью основана на триггерах и никогда не использует тайм-ауты. С другой стороны, эта wiki использует триггеры для одних страниц и таймауты - для других, как например, индекс, который при каждом обновлении страницы будет вызывать ее инвалидацию.

В итоге, наиболее безопасно использовать оба метода. Так, даже если у Вас баг и отсутствует какая-то зависимость, страница обновится по событию.

Проект

CppCMS является основой веб-разработки для выполнения ресурсоемких приложений.

Размещение

SourceForge.net Logo

Поддержать проект

CppCMS needs You


Навигация

Главная страница



Валидация CSS | Валидация XHTML 1.0