## Введение |
|
В отличие от многих существующих сегодня систем кэширования в различных веб-фреймворках на основе понятий "ключ - значение - тайм-аут", кэш CppCMS представляет собой модель "ключ - триггеры - значение - тайм-аут", обеспечивющую простой и эффективный способ инвалидации кэша - держать его согласованным. |
В отличие от многих существующих сегодня систем кэширования в различных веб-фреймворках на основе понятий "ключ - значение - тайм-аут", кэш CppCMS представляет собой модель "ключ - триггеры - значение - тайм-аут", обеспечивающую простой и эффективный способ инвалидации кэша - держать его согласованным. |
|
У каждого закэшированного объекта есть следующие свойства: |
|
1. Уникальный ключ, который идентифицирует его. |
2. Время жизни элемента, которое может быть бесконечным |
1. Уникальный ключ, который его идентифицирует. |
2. Время жизни элемента, которое может быть бесконечным. |
3. Набор триггеров, которые могут инвалидировать кэш. |
|
Например, у нас есть следующий набор триггеров: |
|
![Триггеры](/pics/triggers-scheme.png) |
|
Т.о. мы можем инвалидировать несколько ключей "воскрешением" различных триггеров. |
|
Например, когда мы обновляем статью о `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) для фреймов, но иногда требуется регистрировать события триггеров на уровнях ниже. Например: |
|
- "page" |
- "settings" |
- "local_settings" |
- "global_settings" |
|
Типа: |
|
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](/cppcms_ref/1.0.2/classcppcms_1_1triggers__recorder.html): |
|
Изменим наш код следующим образом: |
|
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](/cppcms_ref/1.0.2/classcppcms_1_1copy__filter.html). |
|
Он используется следующим образом: |
|
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 ... %>`](/wikipp/en/page/cppcms_1x_templates_flow#Caching.Elements). |
|
Например: |
|
<% 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()`, который может выглядить как: |
Т.о. мы добавили контент, который рендерит 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 использует триггеры для одних страниц и таймауты - для других, как например, индекс, который при каждом обновлении страницы будет вызывать ее инвалидацию. |
|
В итоге, наиболее безопасно использовать оба метода. Так, даже если у Вас баг и отсутствует какая-то зависимость, страница обновится по событию. |
|
|