DOC.PROTOTYPES.RU

Главная > Сервера > nginx > ngx_http_proxy_module >

Кешируем блоки HTML при помощи nginx

Не секрет, что пользователи любят, когда контент на сайте обновляется чаще, чем раз в год. Эту любовь пользователей к динамическим страничкам разделяют и поисковики. Google, например, умеет определять наличие обновляющихся блоков на страничке и добавляет ей немного кармы (читай, PR).

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

Простой пример — предположим у вас был замечательный статический HTML, который отдавался nginx-ом со скоростью 2000 раз в секунду (это достижимо без особых проблем). И тут добрый дизайнер нарисовал новый вариант странички, где для залогиненных пользователей присутствует мааааленький блок, содержащий логин, имя-отчество, и, например, аватарку.

Всё, приехали. Статикой тут уже не обойдёшься, а это означает запуск PHP (Perl/Python) на каждый запрос, проверка сессии, ползание по файловой системе (или, ещё хуже, БД) для того, чтобы найти по SID логин и другую инфу о пользователе. Производительность проседает в десять раз. Или нет. :)

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

Фича номер раз, которая в нём есть с незапамятных времён — это SSI. Для того, чтобы её использовать, делаем небольшой скрипт, который умеет принимать из куки идентификатор сессии, и отдавать только тот самый небольшой блок с информацией о пользователе. То есть, на запрос вида GET /get_user_info.php отдаёт фрагмент HTML, вида <div class="login">Здравствуйте, Василий Пупкин</div>.

Соответственно, в самой страничке пишем SSI-include: <!--# include virtual="/get_user_info/" -->.

Для того, чтобы эта конструкция заработала, в конфиг-файле nginx надо описать соответствующий location:

Код (1)
location /get_user_info/ {
    proxy_pass          127.0.0.1:12345/get_user_info.php;
    proxy_pass_header   Cookie; # для передачи sid в Cookie
}

Однако, как вы, наверное, уже заметили, подобная конструкция не решает проблемы с производительностью кода, а лишь позволяет вынести динамическую часть в отдельный скрипт. Окей, как раз для этого нужна фича номер два — кеширование прокси-запросов. В production-ветке она появилась сравнительно недавно, но уже позволяет делать очень многое. :)

Итак, для начала, скажем nginx, где мы хотим хранить наш кеш.

Код (2)
proxy_cache_path    /var/nginx/cache
    levels=         1:2
    keys_zone=      my_cache:64m
    max_size=       1024m
    inactive=       1d;

Эта строчка означает, что мы создаём новую зону для кеша, с названием my_cache, файлы кеша будут лежать в /var/nginx/cache, занимая не более чем 1 гигабайт, и храниться не более чем 1 день.

Теперь скажем nginx, что мы хотим кешировать запросы к нашей локации.

Код (3)
location /get_user_info/ {
    proxy_pass              127.0.0.1:12345/get_user_info.php;
    proxy_pass_header       Cookie; # для передачи sid в Cookie
    
    proxy_cache             my_cache;
    proxy_cache_valid       200 3h; # раз в три часа будем позволять себе обновлять кеш :)
    proxy_cache_valid       any 0; # не кешируем 500 и 400 ошибки
    # если ваш скрипт отдал одну из описанных ошибок, использовать вариант из кеша, если он есть,
    # даже если он уже заэкспайрился
    proxy_cache_use_stale   updating error timeout invalid_header http_500 http_502 http_503 http_504 http_404; 
    proxy_cache_key         "$scheme$proxy_host$uri$is_args$args$cookie_sid";
}

Обратите внимание на последнюю строчку. Именно она позволит нам отдавать каждому отдельному пользователю правильный вариант закешированного блока. Точнее, важен её последний параметр — переменная $cookie_sid, значение которой всегда будет равно значению куки с именем sid, которую вам должен отдать пользователь.

Всё. Теперь nginx сам будет, по мере необходимости, обращаться к вашему скрипту, и кешировать результат его работы. При этом, производительность почти не просядет, и нагрузка на сервер вырастет не очень значительно.

Иван Авсеянко aka Rebus (30.07.2009 г.)
Valid HTML 4.01 Transitional
Copyright © 2011 Сергей Томулевич