УрокОтдаём кэш анонимов без поднятия бэкэнда. Drupal 7 + nginx + memcached.
Помните статью о снижении нагрузки с помощью Memcache Storage? Там Drupal отдавал анонимам кэш на второй фазе бустрапа, что существенно снижало нагрузку на сайт, особенно его посещали преимущественно анонимные пользователи. Там отдача страницы происходила за 50-70мс. Однако я решил этим не ограничиваться, и отдавать кэш вообще без поднятия бэкэнда (на большинстве хостингов это apache). Таким образом мало того, что отдача страницы происходит в десятки раз быстрее, так ещё и не расходуется память на новые процессы апача (или любого другого бэкэнда).
Чтобы понимать, что происходило раньше при отдаче страниц анонимам (по материалам этой статьи), я накидал небольшую схему:
- Клиент вбивает определённый урл в браузер
- Запрос на отдачу страницу по этому урлу направляется на nginx
- Nginx видит, что запрос динамический, и передаёт его дальше на сторону apache
- Apache запускает index.php сайта и начинается фаза бутстрапа в Друпале
- В бутстрапе проверяется – если пользователь аноним, то можно попробовать поискать для него текущую страницу в мемкэше
- Если кэш есть – HTML страница отдаётся пользователю и завершается выполнение запроса.
- Если кэша нет – Друпал начинает полную загрузку и рендер страницы (соединение с базой данных, загрузку блоков страницы, поднятие слоя темизации и так далее).
- По завершению выполнения загрузки страницы, она в отрендеренном виде складывается в мемкэш и показывается пользователю.
Последний пункт означает, что при следующей загрузке этой страницы (пока не истечёт срок годности кэша) пользователь дойдёт только до 6го пункта этого списка, избежав тяжеловесных операций.
Давайте посмотрим на эту схему более наглядно. Вот так выглядит обычный алгоритм обработки запроса, если страница не закэширована:
Наверняка, эта схема всем вполне знакома и понятна. Ещё одна достаточно знакомая схема - когда используется Memcache Storage и при загрузке текущей страницы для неё был найден кэш:
Схема, безусловно, хороша. Однако я предлагаю эту схему усовершенствовать (при условии, что кэш для текущей страницы есть) до следующей:
Теперь алгоритм выполнения запроса будет следующий:
- Как и раньше, клиенту сначала придётся вбить определённый урл в браузер
- Запрос всё так же будет передан на nginx
- Nginx проверяет, можно ли искать данную страницу в кэше. Если да, то он самостоятельно делает запрос в мемкэш и пытается оттуда получить HTML для текущей страницы.
- При успешном завершении поиска страницы в кэше – она сразу же показывается пользователю, и на этом обработка запроса завершается.
- Если кэша нет, то далее следуют пункты 4, 7 и 8 из предыдущего списка.
То есть по сути, мы вообще отдаём пользователю страницу не прибегая к выполнению ни одной строки PHP кода!
Для настройки этой высокопроизводительной связки нам потребуется немного поковыряться в настройках. Давайте перейдём непосредственно к ним.
Конфигурация Memcache Storage
Во-первых, вам надо скачать последнюю версию Memcache Storage 7.x-1.2, т.к. интеграцию туда я добавил совсем недавно.
Во-вторых, вот такие настройки в settings.php необходимы для корректной интеграции:
# Move all cached data (except form cache) to memcache storage. $conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.inc'; $conf['cache_default_class'] = 'MemcacheStorage'; $conf['cache_class_cache_form'] = 'DrupalDatabaseCache'; # Advanced usage of Drupal page cache. $conf['cache_backends'][] = 'sites/all/modules/memcache_storage/memcache_storage.page_cache.inc'; $conf['cache_class_cache_page'] = 'MemcacheStoragePageCache'; # Enable storing of plain HTML text instead of Drupal usual cache object. $conf['memcache_storage_external_page_cache'] = TRUE;
Конфигурация Memcached
Особо вдаваться в подробности не буду, я уже писал про качественную настройку демона memcached, она здесь отлично подойдёт.
Единственное, на чём хотелось бы заострить внимание – я настоятельно рекомендую выносить кэш страниц в отдельную сущность мемкэша. Ниже я написал, почему это необходимо.
Также напоминаю, что наибольшую производительность в интеграции с memcached можно получить имея pecl memcached 2.0.1+ и используя сокеты вместо tcp/udp соединения.
Конфигурация Nginx
Наконец мы подошли к самому интересному – конфигам nginx. Я дописывал интеграцию сразу в конфиг под Друпал, поэтому выкладываю как есть. В комментариях я подробно расписал за что каждая часть отвечает:
server { # Слушаем 80й порт, чтобы запрос сразу попадал на Nginx. listen 80; # Указываем корневую директорию сайта. root /var/www/example.com; # Указываем урл сайта. server_name www.example.com; # Включаем поддержку SSI. # Для взаимодействия Nginx + Memcached это не надо, однако эта технология # очень может помочь при отображении динамических блоков на кэшированных страницах. ssi on; ssi_silent_errors on; # Полностью закрываем доступ к директориям приватной системы. location ~ ^/sites/.*/private/ { return 403; } # Закрываем доступ к скрытым файлам и директориям, чьё название начинается # с точки. Это правило включает в себя директории, используемые системами # контроля версий (Git, Svn и тд). location ~ (^|/)\. { return 403; } # Это правило обрабатывает путь к изображениям, генерируемым с помощью # пресетов в Друпале. Сначала пытаемся отдать файл с помощью Nginx, а если # файл не найден, то передаём запрос на бэкэнд для генерации изображения. location ~ /sites/.*/files/styles/ { try_files $uri @backend; expires 30d; log_not_found off; } # Весь статический контент также отдаём с помощью Nginx. # Если файл не найден - то Nginx отдаст 404 ошибку. Это достаточно # классная фича, т.к. большое кол-во 404 страниц может серьёзно навредить производительности. # Кстати, с этими строками больше нет неоходимости в drupal_fast_404(). location ~* \.(png|gif|jpg|jpeg|css|js|ico|swf|flw|cgi|bat|pl|dll|exe|asp)$ { try_files $uri =404; expires 30d; log_not_found off; } # Сюда должны попадать только динамические запросы. location / { # Сюда будут направлены запросы, которые не удовлетворяют нашим условиям. # Кстати, по поводу условий есть одна очень интересная статья: # http://wiki.nginx.org/IfIsEvil. Прочитав её вы поймёте, почему здесь # я использовал данную конструкцию. error_page 418 = @backend; # Если пользователь авторизован - сразу передаём запрос на бэкэнд. if ($http_cookie ~* "SESS") { return 418; } # Если запрос не GET или HEAD, то также передаём запрос дальше. if ($request_method !~ ^(GET|HEAD)$ ) { return 418; } # Выставляем заголовок для отображения HTML, т.к. кроме нас его # больше будет некому выставить (обычно это делает бэкэнд, но сейчас # до него запрос не дойдёт). default_type text/html; # Если страница будет найдена в кэше, то выставится вот такой заголовок. # Он не несёт в себе никакой нагрузки, это больше для отладки работы # связки в браузере. add_header X-Nginx-Page-Cache HIT; # Ищем текущую страницу в мемкэше. Данную строку желательно оставлять без изменений. # Если вы используете префикс в настройках Memcache Storage, то перед cache_page добавьте # префикс с дефисом. Выглядеть это будет так: # set $memcached_key "PREFIX-cache_page-$scheme://$server_name$uri$is_args$args"; set $memcached_key "cache_page-$scheme://$server_name$uri$is_args$args"; # Здесь указываете адрес memcached сервера. Как видите, и здесь можно использовать сокеты. # Подробнее про это можно почитать тут http://nginx.org/ru/docs/http/ngx_http_memcached_module.html # Очень классной фичей является то, что здесь можно использовать upstream'ы. memcached_pass unix:/var/run/memcached/memcached.socket1; # Если кэш не был найден в мемкэше - передаём запрос бэкэнду. proxy_intercept_errors on; error_page 404 502 = @backend; } # Передача запроса на сторону Apache. location @backend { # Выставляем заголовок, что не получилось вытянуть кэш # из мемкэша. Опять же - он просто для отладки и его можно убрать. add_header X-Nginx-Page-Cache MISS; # Отправляем запрос апачу на порт 8080, который он должен слушать. proxy_pass http://www.example.com:8080/; proxy_connect_timeout 120; proxy_send_timeout 120; proxy_read_timeout 180; # Заголовки, которые будут переданы апачу. proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
Настоятельно рекомендую внимательно прочитать все комментарии в конфиге, т.к. в них раскрывается его сакральный смысл.
Теперь давайте рассмотрим плюсы такой интеграции:
- Нагрузка для анонимных пользователей снижается фантастически - отдача страницы происходит за несколько милисекунд.
- Масштабируемость. Данное решение сохраняет все преимущества масштабирования мемкэша. Правда, в этом случае надо будет немного допилить конфиг (настроить апстримы), однако это делается слишком просто, поэтому за проблему не считается.
- У вас есть статистика по использованию памяти в мемкэше (например, phpMemcachedAdmin). При необходимости вы сможете покопаться там и подебажить хранимые страницы, “пощупать” как оно лежит в оперативной памяти.
- Вы можете настраивать кэш страниц как вам удобно, используя все доступные для этого модули Друпала. Например, интеграция с Cache Expiration может сделать эту связку неимоверно эффективной.
- Вы можете легко самостоятельно управлять кэшем страниц через стандартные функции ядра для работы с кэшем. Это даёт немаловажную возможность тонкой настройки работы с кэшированными страницами.
- Пожалуй, самое минимальное время отдачи страницы. Ещё в прошлом году Ромка писал, цитирую “Думаю, посоперничать в производительности с Варнишем могло бы только решение со сязкой nginx + memcache, о которой я говорил выше, но такого готового решения под Друпал нет и в условиях постоянной нехватки времени Варниш является максимально удобным решением по соотношению скорость работы / скорость внедрения.” Собственно, вуа-ля.
- Наиболее рациональное использование памяти, по сравнению с другими аналогами. Любое перемещение файлового кэша в оперативную память, безусловно, даёт преимущество. Однако мне даже сложно сравнивать этот вариант, со специально заточенной под это системой, с продуманной фрагментацией памяти, заточенной под максимальную скорость отдачи.
Конечно, эта связка имеет и свои недостатки. Они, как и преимущества, заключаются в архитектуре мемкэша:
- Нет возможности очистки по wildcard. То есть я не могу очистить страницы, которые начинаются, например, на news*. Это связано с тем, что мемкэш этого просто не поддерживает. Внутри Друпала это можно обработать, сделав проверку на указанные wildcard’ы после получения кэша. Однако на стороне nginx это сделать невозможно. Поэтому если вы хотите удалить большое количество страниц, вам придётся для каждой страницы вызвать cache_clear_all() отдельно. Это не большая проблема (т.к. работает быстро), но это надо учитывать при построении архитектуры с данной связкой.
- Теряется фича с очисткой всего хранилища {cache_page}. Снова из-за того, что мемкэш не поддерживает очистку по wildcard. Это связано с тем, что когда кэш используется внутри Друпала, то мы можем проворачивать некоторые финты ушами, которые помогают имитировать сброс кэша. А вот nginx этого уже сделать не может. Однако и здесь есть свой выход - создавать отдельный инстанс мемкэша под кэш страниц. В конфиге мемкэша, который я уже ранее выкладывал, это сделано . Таким образом, вы снова сможете сбрасывать весь кэш страниц при необходимости.
- Очевидный минус - это сложность в настройке. Моя оценка может быть несколько субъективной, но мне всё же кажется, что у неопытного разработчика настройка связки nginx + memcached с незаурядной конфигурацией может вызвать некоторые затруднения.
- Простые хостинги это решение не потянут, т.к. оно серьёзно затрагивает серверную конфигурацию.
Вот так эта связка выглядит для меня. Очевидно, что для больших проектов это далеко не единственное улучшение, которое придётся сделать. Скорее всего придётся подключить SSI для блоков, которые надо отображать в реальном времени, не взирая на кэш. Скорее всего, придётся устанавливать модули, которые помогут использовать механизм кэширования страниц на полную катушку. Однако это не является темой данной статьи.
Напоминаю, что данный метод не является средством оптимизации сайта. Это хороший инструмент в снижении нагрузки, и это важно понимать. С такой связкой не страшен даже хаброэффект. А если немного подкрутить конфиг - то можно даже отдавать страницу из кэша и для авторизованного пользователя если, например, бэкэнд упал (это очень напоминает одну из фич варниша).
Почему не нативный кэш nginx?
Я неоднократно слышал вопрос - “почему ты просто не использовал нативный кэш nginx?”. Я думаю, что ответ кроется в 2-7 пунктах из “плюсов” связки. Разве нативный кэш nginx может это превзойти, или хотя бы повторить эти преимущества? Для меня ответ очевиден.
Похвали себя сам
В конце статьи я хотел бы обратить внимание на то, что, судя по всему, я оказался первым, кто сделал подобную связку. Вот почему мне так кажется:
- В гугле нет подобного материала по запросу drupal + memcached + nginx. Есть просто включение мемкэша рядом с настройкой nginx, но никаких намёков на интеграцию между ними.
- Раньше был только один модуль, позволяющий интегрировать Друпал с мемкэшем - и это Memcache API. Однако он хранит в мемкэше кэш в виде сериализованных объектов, а nginx не умеет десериализовывать данные и использовать php объекты. Можно, конечно, скомпилировать nginx с поддержкой perl сниппетов и попробовать дописать эту недостачу, однако такого решения я также не нашёл.
Упоминание про новую связку в интеграции с Друпалом здесь не просто так. Я веду к тому, что вы будете фактически первоиспытателями этой связки в Друпале, и информацию по этому вопросу искать в интернете бесполезно - придётся задействовать соображалку :)
- Spleshka
- 01.09.2013
- 60611
Комментарии
а если вместо nginx использовать varnish?
в смысле varnish +memcache
Не вижу принципиальной разницы. Просто nginx + apache - наиболее распространённая связка, вот я под nginx и написал конфиг. Можно тоже самое сделать и под varnish. Тут основной идеей было научить друпал складывать кэш страниц в мемкэш в виде текста, а не сериализованных объектов.
а ежели на сервере мало оперативки?
Это я к тому ,что варниш умеет хранить свой кеш и в памяти и в файловой системе
можно обойтись одним варнишем без мемкеша - вообщем вариантов масса
только как подобрать оптимальный?
mecache во-первых, начинает терять данные на высокой нагрузке, во-вторых, не предназначен для хранения больших объёмов данных
также можно рассмотреть и редис
@varnish_fun,
Ну смотря что значит мало. 512мб вполне хватит для сайта внушительных размеров. Я считал - если взять за средний размер страницы 70кб, то туда влезет около 7.5 тысяч страниц.
Безусловно, любой из вариантов годен. Использовать варниш в качестве реверсивного прокси можно, и тогда уже по большому счёту без разницы где хранится кэш - на диске или в памяти. Просто я предлагаю уже готовое решение под Друпал, с поддержкой всех модулей, управляющих кэшем страниц. Под варниш тоже, кстати, можно использовать модуль Cache Expiration для очистки страниц из кэша.
Оптимальная подборка строится из ваших серверных конфигураций и требований сайта, общей рекомендации дать не смогу.
>> mecache во-первых, начинает терять данные на высокой нагрузке
>> во-вторых, не предназначен для хранения больших объёмов данных
Кто вам сказал этот бред? Он прекрасно используется, например, вконтакте и на фэйсбуке. Разве там маленький объём данных?
Отличный метод!
Но годиться, к сожалению, далеко не для всех страниц. Например как это будет работать с формой комментариев, или банерной? Полагаю никак :)
Так же возникнут проблемы со статистикой просмотров страниц и другими "фенечками" Друпала выполняемые через hook_exit. Нуден механизм дёрнуть этот самый хук в друпале в любой сохранённой странице. Поправьте, если не прав.
Почему это не будет работать с формой комментариев? Всё будет фунициклировать как и положено.
Банерную систему в таких случаях я внедрял с помощью SSI, тоже проблем не возникало.
Конечно, hook_exit() не будет работать, и соответственно, считаться кол-во просмотров. Но всё это можно реализовать либо на SSI, либо на JS, это совсем не сложно. Такое решение того стоит :)
Форма коментариев с капчей????
Сказать, как сделать, или в чём вопрос? :)
Да, было бы очень полезно )
А почему Вы делаете акцент на memcache?
Ведь в памяти можно хранить и с помощью того же apc ,который к тому же присутствует на многих шаред хостингах
в чем преимущество мемкеша перед другими key-value хранилищами?
Мне нравятся инструменты для работы с мемкэшем (например, тот же phpMemcachedAdmin). Мне нравится его простая и удобная масштабируемость. Мне нравится то, что я полностью знаю его архитектуру. Мне нравится то, что для него написана интеграция со всем, чем нужно. Мне нравится как он фрагментирует память. Мне нравятся его алгоритмы работы при заполненной памяти. Мне нравится подробная и доступная документация. Я могу продолжать достаточно долго :)
ключевое - "Мне нравится то, что я полностью знаю его архитектуру"
1. С чего ты взял, что ключевое? Я изучил архитектуру, потому что мне понравилось реализация.
2. Любой выбор использования какого-либо компонента всегда вызовет вопрос "Почему это, а не то?". Выбор компонента решается при выполнении определённых задач и архитектурных задумок.
3. На данный момент для моих задач по совокупности критериев побеждает мемкэш, поэтому и статьи про него пишутся. Понадобится ещё что-то - будут другие статьи :)
Spleshka, не можете ли посоветовать специалиста, который переведёт кэширование из MongoDB в memcache storage?
Устал уменьшать размер файлов Mongo.
А в чём проблема с переносом кэша? Просто переписать пару строк кода в settings.php и всё. Примеры этих строк есть в README.txt модуля memcache_storage. Если есть вопросы - спрашивайте, помогу.
Убрать отсюда апач и всё будет намного лучше...
Какой формат у memcache?
Не понятен вопрос. Какой ещё формат?
поднимаю связку nginx-memcache
вроде все сделал по статьям но nginx отдает кашу когда страницу отдает от memcached, т.е.
X-Nginx-Page-Cache HIT - каша
при первом
X-Nginx-Page-Cache MISS - все ок
убери сжатие кэшированных страниц в настройках друпала
Была такая же трабла. Отключил сжатие. Для одной страницы - помогло. Другая же, почему-то так и осталась - из кэша как попало отображаться :( А так очень интересно - попробую еще поковырятся.
А есть возможность портировать Ваш модуль на 6.х?
у меня тоже проблема с .gz файлами.. Как можно решить такую проблему? это происходить когда активирована опция .gz с мод. Advagg..
Очень интересно ваше решение. Прошу кратко объяснить как сделать так, чтобы необходимые блоки на страницах не кешировались, или время жизни кеша отдельных блоков было минимальным около 5 минут. Также интересно как сохранить возможность вести статистику просмотров страниц. Это необходимо для новостного сайта.
Огромное спасибо!
Для того чтобы не кешировать отдельные блоки можно использовать SSI (nginx), либо ESI (varnish)
отключил adBlock , тыкнул на рекламу. Большое спасибо за статью
Евгений, спасибо за модуль!
Главная проблема в связке с nginx - это конечно отсутствие возможности очистки кэша по wildcards.
В моем случае не хватает очистки кэша страниц пейджеров (?page=N). Не планируете ли добавить такую функцию в Cache Expiration?
Вопрос снимается.
Нашел Cache expiration API, решил сам:
Добрый день.
Не могу разобраться почему у меня никак не отдает страницы из кэша.
Использую связку nginx + php-fpm + memcached
Модуль установлен и работает судя по отчету корректно
[IMG]http://my.jetscreenshot.com/21512/m_20140512-oy0y-26kb.jpg[/IMG]
Сокет memcached существует
Конфиг Nginx такой
В настройка производительности drupal кэш для анонимов включен
[IMG]http://my.jetscreenshot.com/21512/m_20140512-tnfx-36kb.jpg[/IMG]
Но все страницы идут через бэкенд и более того по какой то причине когда я авторизован страница admin/config погружается минуты 2, если закоментировать строки с поиском страницы в кэше то отрывается нормально (хотя по логике для авторизованного пользователя вообще не должен происходить поиск страницы в кэше)
Сегодня заработало, никаких изменений в настройках nginx и memcashed не делал, возможно ли что не работало из за маленького значения memory_limit в php.ini (было 128 изменил на 256)?
Еще такой момент, есть 2 станицы на которых собирается материал с RSS лент, как сделать так что бы для них кэш сбрасывался при каждом запуске крон (с помощью модуля Cache Expiration такое по видимому не реализовать) или чтобы эти страницы не кэшировались вовсе.
Решил с помощью модуля Rules и добавления правила запуска функции
при каждом запуске cron
а если у меня не стоит apache, только nginx? как мне изменять настройки
Не могу заставить работать с урл с аргументами по русски. Конкретно:
Есть views с аргументом, передающимся через URL страницы вида site.ru/razdel/аргумент%20идет
В мемкеше вижу, что page cache для таких урлов успешно создаётся, однако nginx их в упор не видит, при попытке загрузить их из мемкеша.
Конфиг в nginx почти 1 в 1 ваш. Гуглил на эту тему, народ пишет мол в nginx переменная $uri с русскими урлами у всех работает корректно...
Куда копать дальше? :)
Поставили мемкеш. Сайт имеет так же и мобильную тему отдельную (десктопная и мобильная темы переключаются). Проблема в том, что контент некоторых страниц при просмотре с ПК выдается в формате мобильной версии.
Такая же проблема была поначалу и с друпаловским кешированием. Решили путем создания в БД копий кешовых таблиц (с приставкой m_) и в папке мобильного домена (находящейся в корне директории /sites) в settings.php указали эти новые кешовые таблицы.
Теперь вопрос реализовать подобное разграничение в кешировании мемкеша - одно для десктопа и другое для мобайла.
Как быть? Спасибо.
Думаю использовать у себя на сайте - http://takeandlive.com.ua. Насколько может ускорить?
Комментировать