Александр Шуменко - varnish for authenticated users
TRANSCRIPT
О чём мы будем говорить
◻ Varnish Reverse Proxy
◻ Принципы конфигурации Varnish
◻ Зачем и как разделять кэш
◻ Кэширование для пользователей
◻ Кэширование для ролей
◻ Очистка кэша
❑ Browser – Varnish – Web server
❑ Жизнь запроса внутри Varnish
❑ Конфигурация Varnish (*.VCL файл)
Varnish Reverse Proxy
Browser – Varnish – Web server
Browser
Varnish
Cache
First
request
2nd (nth )
request
❑ Первый запрос попадает к web серверу❑ Остальные запросы обслуживаются из кэша
Web
server
Скорость работы Varnish
Условия теста: $ ab -c10 -n50 http://mysite.my/
Условия теста Время теста (с) Время запроса (с)
Web server (вся страница) 62.301 1.246
Web server (bootstrap) 23.041 0.461
Web server (return) 0.048 0.001
Varnish (Холодный кэш) 1.121 0.022
Varnish (Горячий кэш) 0.029 0.0006
Varnish
Жизнь запроса внутри Varnish
Cache
vcl_recv{}
lookup
vcl_hash{} Web
server
pass
vcl_deliver{}
vcl_fetch
{}
hit cache
miss cache
Request (HTTP)
Response
Конфигурация Varnish
(vcl_recv)
Нормализировать данные для web-
приложения.
Выбрать политику кэширования.
Управление доступом.
sub vcl_recv {
/* Add custom header. */
set req.http.X-Forwarded-For = client.ip;
/* Do not cache POST. */
if (req.request == “POST”) {
return (pass);
}
/* Do not cache authenticated users and Authorization
request. */
if (req.http.Authorization || req.http.Cookie) {
return (pass);
}
/* Mark other request as cacheable. */
return (lookup);
}
Конфигурация Varnish
(vcl_hash)
Возвращает ID
запроса.
Позволяет иметь разный кэш одной и той же страницы.
ВАЖНО: Hash
строится только из переменных запроса.
sub vcl_hash {
/* Make page ID based on request. */
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
}
else {
hash_data(server.ip);
}
return (hash);
}
Конфигурация Varnish
(vcl_fetch)
Указать время кэширования страницы.
sub vcl_fetch {
/* Cache TTL can be controlled from backend. */
if (beresp.http.cache-control !~ “s-maxage”) {
/* Cache JPG images for 60 seconds. */
if (req.url ~ “\.jpg$”) {
set beresp.ttl = 60s;
}
}
/* Unset Cookies for cached pages. */
if (beresp.ttl > 0s) {
unset beresp.http.Set-Cookie;
}
return (deliver);
}
❑ Кэширование per-user
❑ Кэширование per-role
❑ User-blocks для per-role кэширования
Зачем и как разделять кэш
Кэширование per-user:
VCL functions
На практике почти не применимо само по себе.
В большинстве, пользователи посещают разные страницы
Размер кэш базы может колоссально возрасти
sub vcl_recv {
/* Remove cookie from pages that should be same for all
users. */
if (req.url ~ "\.(png|gif|jpg|swf|css|js)$") {
unset req.http.cookie;
}
}
sub vcl_hash {
/* Add User specific (session) Cookie to page ID. */
if (req.http.Cookie ~ "^.*?SESS([^;]*);*.*$") {
hash_data(regsub(req.http.Cookie, "^.*?SESS([^;]*);*.*$",
"\1"));
}
}
Кэширование per-role:
Drupal Hooks
В Drupal необходимо установить Cookie уникальное для роли пользователя (комбинации ролей пользователя). Используем hash() и шум для усложнения подбора Cookie чужой роли.
/** Implements hook_user_login(). */
function hook_user_login($edit, $user) {
$roles = array_filter(array_keys($user->roles));
sort($roles);
$bin = hash('sha256', implode('_', $roles) . 'SECRET_KEY_ABC');
$params = session_get_cookie_params();
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
setcookie('BIN', $bin, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']);
}
/** Implements hook_user_logout(). */
function hook_user_logout() {
$params = session_get_cookie_params();
setcookie('BIN', '', REQUEST_TIME - 3600, $params['path'], $params['domain']);
}
Кэширование per-role:
VCL functions
В Varnish необходимо добавить этот Cookie к ID (hash) всех запросов.
sub vcl_recv {
/* Remove cookie from pages that should be same for all users. */
if (req.url ~ "\.(png|gif|jpg|swf|css|js)$") {
unset req.http.cookie;
}
}
sub vcl_hash {
/* Add Role specific Cookie to page ID. */
if (req.http.Cookie ~ "^.*?BIN=([^;]*);*.*$") {
hash_data(regsub(req.http.Cookie, "^.*?BIN=([^;]*);*.*$", "\1"));
}
}
Кэширование per-role:блоки “паразиты”
(user blocks)
Необходимо сделать механизм получения информации специфической для каждого пользователя (user links, корзина, избранное, личные сообщения и т.д.).
Кэширование per-role:дополненный vcl_hash
Нужен путь который будет возвращать всю персональную информацию пользователя. Его нужно кэшировать per-user.
sub vcl_hash {
if (req.http.Cookie ~ "^.*?BIN=([^;]*);*.*$") {
/* Require session Cookie. */
if (req.http.Cookie ~ "^.*?SESS([^;]*);*.*$") {
/* Enable per-user cache for selected path. */
if (req.url ~ "^/user_blocks.*$") {
hash_data(regsub(req.http.Cookie, "^.*?SESS([^;]*);*.*$", "\1"));
}
} else { error 750 “Sanity error”; }
hash_data(regsub(req.http.Cookie, "^.*?BIN=([^;]*);*.*$", "\1"));
}
}
User-blocks для per-role кэширования:Основные вопросы при проектировании
◻ Решить как получать данные (AJAX vs ESI).
◻ Как держать кэшированную версию /user_blocks актуальной («правильно» чистим кэш в varnish).
◻ Адаптировать чужие модули (на примере flag
модуля)
User-blocks для per-role кэширования: получаем данные AJAX
◻ Простота
◻ Один запрос независимо от количества боков
◻ Страница «мигает»
◻ Пользователь видит лишние запросы (и технические URL)
◻ Не работает с выключенным JS
За Против
Browser
Varnish
Per-role
cache
Per-user
cache
Main
request
AJAX
response
<span class=“user-blocks" data-arg="…"></span>
Web
server
User-blocks для per-role кэширования:
получаем данные ESI
◻ Пользователь видит готовую страницу
◻ Не зависит от включенного JS*
◻ Каждый блок создаст свой запрос в Drupal*
◻ Сложность реализации
За Против
Browser
Varnish
Per-role
cache
Per-user
cache
Main
request
ESI
<!--esi <esi:include src="/user_blocks/... " /> -->
Web
server
User-blocks для per-role кэширования: включаем поддержку ESI в Varnish
Чтобы Varnish обработал ESI, необходимо ему явно указать сделать это для каждой страницы которая использует ESI. Лучше помечать такие страницы в Drupal каждый раз когда вы генерируете ESI тэг.
Varnish:
sub vcl_fetch {
/* Check if we should process ESI on this page. */
if (resp.http.DOESI == "1") {
set beresp.do_esi = true;
}
}
Drupal:drupal_add_http_header(‘DOESI’, 1);
User-blocks для per-role кэширования:чем можно чистить кэш в varnish
sub vcl_hit {
if (req.request == "PURGE") {
purge;
error 200 "Purged.";
}
}
sub vcl_miss {
if (req.request == "PURGE") {
purge;
error 200 "Purged.“;
}
}
sub vcl_xxxx {
ban("req.url ~ ^/user_blocks.*$");
ban("obj.http.myheader == myvalue");
}
◻ Для ban доступны любые метаданные.
◻ ban фильтрует только уже кэшированные объекты, и не мешает новым попасть в кэш.
◻ ban можно вызвать из терминала.
◻ Позволяет «тегать» кэш.
Purge Ban
User-blocks для per-role кэширования:тегаем Varnish
кэш из Drupal
Лучше собирать все теги в статик переменную, и выводить в заголовки ответа в deliverycallback страницы. Но можно и иметь разные заголовки для разных тегов.
/* Page callback for /user_blocks. */
function mymodule_page_user_blocks() {
global $user;
drupal_add_http_header("userblocks", $user->uid);
/* Prepare user block data. */
…
}
User-blocks для per-role кэширования:очищаем Varnish кэш из Drupal
Используем API модуля varnish (https://drupal.org/project/varnish) для отсылки команд в терминал Varnish. Для этого необходим PHP с --enable-sockets.
/** Purge user blocks for specific user. */
function mymodule_purge_user_blocks($uid) {
_varnish_terminal_run(array("ban obj.http.userblocks ~ \"$uid\""));
}
/** Implements hook_flag(). */
function mymodule_flag($op, $flag, $content_id, $account, $fcid) {
/** Invalidate user blocks (user favorites block and list) */
mymodule_purge_user_blocks($account->uid);
…
}
User-blocks для per-role кэширования:очищаем
Varnish кэш во время запроса.
Если нету доступа к терминалу Varnish, можно очистить кэш user-blocks прямо во время запроса, если в качестве тега использовалась имя сессии.
sub vcl_recv {
/* If user make flag action we need to clean up user-blocks cache. */
if (req.url ~ "^/flag/.*$") {
/* Require session Cookie. */
if (req.http.Cookie ~ "^.*?SESS([^;]*);*.*$") {
/* Clean user-blocks for this session. */
ban("obj.http.userblocks == " + hash_data(regsub(req.http.Cookie, "^.*?
SESS(\w*);*.*$", "\1")));
} else { error 750 “Sanity error”; }
return(pass);
}
}
Полезные ссылки
Описание работы Varnish и VCL
◻ https://www.varnish-cache.org/docs/3.0/reference/vcl.html
◻ https://www.varnish-software.com/static/book/VCL_Basics.html
Полезные модули Drupal.org
◻ https://drupal.org/project/varnish - доступ к терминалу Varnish, позволяет быстрее начать с ним работу.
◻ https://drupal.org/project/esi_api - API для создания ESI тегов, встроенный фоллбек на AJAX.
Спасибо за внимание!