Главная > Теория и практика > Nginx + Memcached + SSI - кеширование страниц и блоков (partials)

Nginx + Memcached + SSI - кеширование страниц и блоков (partials)

nginx-logo

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

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

Довольно детально принципы кеширования страниц и блоков были рассмотрены в предыдущей статье (Varnish + ESI).

В качестве краткого повторения, несколько основных идей и преимуществ кеширования на уровне странц:

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

Nginx и memcached

Nginx позволяет читать данные из Мemcached - для этого Вам необходим модуль HttpMemcachedModule.

Самый простой пример - проверяем есть ли страница в кеше и, если есть, достаем из кеша, иначе - делаем запрос к бекенду.

server {
  location / {
    set $memcached_key $uri; # Ключ для проверки в memcached
    memcached_pass     127.0.0.1:11211; # Параметры подключения
    default_type       text/html; # Заголовок по умолчанию
    error_page         404 = @fallback; # 404 - данные в кеше не найдены
  }

  location @fallback {
    proxy_pass backend; # Бекенд
  }
}

В данный момент Nginx не умеет сохранять значения в memcached - только читать данные. Значит задача по сохранению страниц в кеш ложится на бекенд. Выглядит это приблизительно так (PHP):

<?
$memcache = new Memcache();
# какой-то код

ob_start();
# визуализация страницы - html
$html = ob_get_clean();

$memcache->set($_SERVER['REQUEST_URI'], $html);
echo $html;
?>

Кеширование блоков и SSI

Если у Вас не самый примитивный сайт (а он у Вас не такой), логика кеширования страниц будет несколько усложнена различными динамическими элементами страницы (блок авторизации, динамическое меню и т.п.). Подобные задачи решаются c помощью SSI - server side includes. Кроме всего прочего использование SSI экономит память, т.к. для каждой страницы Вы не будете хранить ее исходник целиком, а только внутреннюю (изменяемую) часть.

Синтаксис SSI очень простой и выглядит так:

<!--# include virtual="/authentication.php" -->

Следует отметить, что SSI - это уровень Web сервера. Для прилжения этот механизм полностью прозрачен. В приведенном примере, на месте вызова SSI, Web сервер (nginx) просто сделает еще один запрос к бекенду. Например, если у Вас два SSI вызова на страницу, то клиентский запрос к каждой странице будет генерировать три запроса к бекенду. Конечно, само по себе это бессмысленно, но в связке с кешированием представляет из себя мощный инструмент оптимизации систем.

Практический пример

Теперь соберем все выше изложенное в последовательный рецепт решения рассматриваемой задачи.

Для начала Вам необходимо выделить блоки в Вашем базовом (глобальном) шаблоне и заменить их вызовами SSI. Пример на PHP:

<html>
<body>

<h1>Тестируем nginx + memcached + ssi</h1>

<div class="auth">

<!--# include virtual="/authentication.php" -->
</div>


<!--# include virtual="/somepage.php" -->

</body>
</html>

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

Теперь необходимо настроить nginx для обработки SSI вызовов и использованию memcached:

# Upstream бекенда
upstream backend {
    server 127.0.0.1:8081;
}

server {
	listen 80;
	server_name test.com;

	root /var/www/test;
	index index.php;

	location / {
		# Все POST запросы отправляем на бекенд (не кешируя)
		if ($request_method = POST) {
			proxy_pass http://backend;
			break;
	        }

		# Включаем обработку SSI
		ssi on;
		default_type text/html;

		# Проверяем в мемкеше
		set $memcached_key "$uri";
		memcached_pass localhost:11211;
		proxy_intercept_errors  on;
		error_page 404 502 = @process;
	}

	# Сюда запрос приходит, если его небыло в кеше
	location @process
	{
		proxy_pass http://backend;
		ssi on;
	}
}

# Обычный бекенд
server {
	listen 8081;

	location ~* \.(php)$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /var/www/test$fastcgi_script_name;
    }
}

Следующим шагом в приложении необходимо будет реализовать сохранение исходников страниц в memcached.

Кеширование персонализированных блоков

Ясно, что для кеширования персонализированных блоков необходимо добавить идентификатор пользователя (не вздумайте использовать внутренний ID пользователя) в ключ memcached. SSI вызов необходимо пометить, чтобы отличать персональные блоки от общих. Т.е. персональный вызов SSI будет иметь такой вид:


<!--# include virtual="/uid/authentication.php" -->
</div>

Для генерации memcached ключей с идентификатором пользователя можно использовать куки (устанавливать его будет приложение при авторизации пользователя):

set $memcached_key "$uri:$cookie_uid";

Соответственно, необходимо в nginx добавить обработчик для “/uid” запросов:

        location /uid {
                ssi on;
                default_type text/html;

                set $memcached_key "$uri:$cookie_uid";
                memcached_pass localhost:11211;
                proxy_intercept_errors  on;
                error_page 404 502 = @process;
        }

Естественно, в самом приложении все “/uid” запросы должны кешироваться с идентификатором пользователя:

$m->set($_SERVER['REQUEST_URI'] . ':' . $_COOKIE['uid'], $html);

Следует обратить внимание

Недавно появился продукт, который должен стать типичным решением при кешировании страниц - Twicecache. Пока он на очень ранней стадии и доступны только его исходники. Будем внимательно следить.

Google Bookmarks Digg I.ua Ru-marks Ruspace Zakladok.net Reddit delicious Technorati Yahoo My Web News2.ru БобрДобр.ru Memori.ru rucity.com

Статьи по теме

  1. ash2k
    11 Апрель 2010 в 10:04 | #1

    error_page 404 @fallback;
    должно быть
    error_page 404 = @fallback;
    чтобы HTTP код был не 404, а такой, какой должен быть в случае промаха мимо кеша

  2. 12 Апрель 2010 в 11:57 | #2

    @ash2k
    Спасибо, исправил ошибку

  3. KOT
    5 Июнь 2010 в 15:43 | #3

    Настроил все согласно вашему примеру, -все замечательно работает за одним исключением.

    На запросы вида http://site/ nginx выдает 404, если зайти на http://site/index.php все отрабатывает (ответ ложится в кеш) и http://site/ тоже начинает работать. После очистки кеша nginx снова отдает 404 на http://site/

  4. 5 Июнь 2010 в 23:53 | #4

    @KOT
    Похоже, что пропущен “index index.php;” - добавьте это во все виртуальные хосты, где это актуально

  5. KOT
    6 Июнь 2010 в 07:23 | #5

    Не хватает
    root /var/www/test;
    index index.php;
    в
    # Обычный бекенд
    server {
    listen 8081;

    location ~* \.(php)$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME /var/www/test$fastcgi_script_name;
    }

  6. KOT
    6 Июнь 2010 в 08:50 | #6

    Кроме того nginx при вызове http://site/ в $uri будет “/index.php” а не “/” как в php $_SERVER['REQUEST_URI']. По этому нужно либо в nginx заменить $uri на $request_uri либо в PHP заменить $_SERVER['REQUEST_URI'] на $_SERVER["DOCUMENT_URI"] - тогда индексы будут кешироватся и обрабатываться правильно.

    P.S. 2Admin: Удалите плиз мои комментарии от 07:15 и 07:17 (напостил лишних так как отправленные не отображались).

  7. Начинающий
    28 Июнь 2010 в 20:38 | #7

    Денис, скажите пожалуйста, обязательно ли ставить Nginx на отдельную машину или же его использование будет эффективным даже на одной машине с Апачем? Спрашиваю потому, что сейчас у нашего проекта средств хватит только на один сервер, но мы сразу хотим поставить предложенную вами связку фронтенд+бекенд+кеш

  8. 28 Июнь 2010 в 21:48 | #8

    @Начинающий
    Вовсе не обязательно. Nginx гораздо лучше подходит (намного эффективнее) для отдачи статики (картинок, файлов стилей и т.п.), поэтому даже на одном сервере лучше использовать связку Nginx + Apache чем просто Apache. В Вашем случае Nginx будет принимать все запросы на себя - статику будет отдавать сам, а запросы к php будет перенаправлять на Apache (который, скажем, будет слушать 81 порт). Обратите внимание на настройки количества процессов в Apache - важно, чтобы они умещались в памяти и не вылазили в своп.

  9. Начинающий
    2 Июль 2010 в 00:27 | #9

    Спасибо за ответ, Денис. Продолжая изучать эту тему я наткнулся на еще один способ ускорения - серверную утилиту eAccelerator, которая ускоряет запуск php-скриптов. Скажите, а не конфликтует ли она с Nginx и Memcached?

  10. 2 Июль 2010 в 10:56 | #10

    @Начинающий
    Утилита отличная, советую использовать! На nginx и memcached никак не влияет и не конфликтует :)

  1. Пока что нет уведомлений.