Тяжёлый back-end своими руками
Материал из Eludia.
Содержание |
В данной статье описывается один из вариантов настройки тяжёлого back-end'а на базе Eludia.pm, который можно использовать в комплекте с различными реверсными proxy-серверами. Критериями оптимизации в данном случае являются гибкость настройки, управляемость и защита от перерасхода ресурсов сервера (прежде всего, оперативной памяти).
Предыстория вопроса
Идея выделения "тяжёлого back-end'а" у сервера WEB-приложения применяется и развивается, пожалуй, на протяжении всей истории динамического WEB-программирования. Сочетание таких факторов как:
- требование большой конкурентности;
- большой процент запросов на статические файлы в общем потоке обращений;
- весьма малые ресурсы, потребные для обслуживания статического запроса;
- чрезвычайно большие ресурсы, потребные для обслуживания динамического запроса
естественно приводит к мысли о том, чтобы обслуживать статические и динамические запросы серверными процессами, у которых разделены как минимум адресные пространства. В конце 1990-х годов применялись конфигурации с 2 и более инсталляциями Apache 1.3, был активизирован mod_proxy, а у другой — например, mod_perl.
Эволюция mod_proxy сначала привела к возникновению специализированных Apache-модулей, таких как mod_accel, а затем отдельных серверов для высокопроизводительного реверсного проксирования: nginx, lighttpd, varnish.
Другие варианты развития той же идеи связаны с варьированием способов связи между front- и back-end'ом:
- применение протоколов, отличных от HTTP (прежде всего, FastCGI и его аналогов);
- общение не через TCP (как с применением FastCGI, так и безотносительно к нему: например, mod_pipe).
Минимальный WEB-сервер
Eludia.pm содержит собственный однозадачный WEB-сервер, базирующийся на стандартном Perl-модуле HTTP::Daemon и поэтому не требующий установки какого-либо ПО, кроме Perl. Например, для 8000-го порта он запускается командой
perl -MEludia::Content::HTTP::Server -e"start (':8000')"
в контексте директории приложения. В таком варианте сервер вполне пригоден для отладки, однако обладает 3 существенными недостатками:
- однозадачный;
- медленно обрабатывает статические запросы;
- не отдаёт единожды занятую память ОС (что в классическом mod_perl решается методом "рождения и гибели": заданием конечного MaxRequestsPerChild).
Запуск в цикле
Последняя проблема решается тривиально: при помощи скрипта, который в бесконечном цикле запускает тот же WEB-сервер, указывая ему максимальное количество запросов (например, 100):
while [ 1 ]; do
perl -MEludia::Content::HTTP::Server -e"start (':8000', 100)" 2>>logs/error.log
done
Исчерпав ресурс, сервер отправляется в перезагрузку. При использовании такой схемы напрямую может возникнуть возникнуть проблема: рестартующий сервер может быть сочтён несуществующим.
Настройка реверсного proxy
Такого рода затруднения отлично решаются современными multithreaded-серверами с серьёзной поддержкой реверсного проксирования и высокой эффективностью в работе со статикой. Это и сглаживает перебои, и вообще заметно разгружает back-end.
nginx
По нашим ощущениям, лучшим ПО такого рода на сегодня является nginx. Фрагмент его конфигурации для рассматриваемого случая имеет вид:
upstream my_application {
server 127.0.0.1:8000 max_fails=3 fail_timeout=30s;
}
sever {
listen 80;
root /var/projects/my_application/docroot;
location /i/ {
expires 30d;
}
location = / {
proxy_pass http://my_application;
proxy_set_header X_Forwarded_For $remote_addr;
proxy_buffering off;
}
location / {
return 403;
}
}
Apache 2
Впрочем, увы, nginx доступен не столь повсеместно, как хотелось бы. В частности, под Win32 он пока доступен в несколько урезанном варианте: с установкой в фиксированный каталог и, главное, без возможности запуска в качестве сервиса.
А вот Apache 2 как раз изначально задуман так, чтобы на каждой платформе выжимать максимум из местных достопримечательностей. И этим вполне можно воспользоваться:
DocumentRoot "c:/Program Files/my_application/docroot" ProxyPassMatch ^(/.*/.*) ! ProxyPass / "balancer://my_application/" maxattempts=3 timeout=2 <Location /i> SetHandler default ExpiresActive on ExpiresDefault "now plus 1 days" AddDefaultCharset windows-1251 </Location> <Proxy balancer://polouchka> BalancerMember http://127.0.0.1:8000 </Proxy>
А кому-то нравится Apache 2 под UNIX. Ради-бога.
В любом случае — не забыть активировать модули:
LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_balancer_module modules/mod_proxy_balancer.so LoadModule proxy_http_module modules/mod_proxy_http.so
И, да, разумеется, ставить bash под Windows вовсе не обязательно, но об этом чуть ниже.
Многозадачность
Задействовав, помимо 8000, ещё 1-2 порта, и добавив пару строк в конфигурацию proxy:
upstream my_application {
server 127.0.0.1:8000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8002 max_fails=3 fail_timeout=30s;
...
}
мы получим уже полноценный отказоустойчивый сервер приложения.
Скрипты запуска / останова
А как же "задействовать эти порты"? Не запускать же все процессы вручную. Для этого мы напишем 3 скрипта. 1 изних назовём my_application_loop.sh, его содержимое приведено выше.
Второй — запуск my_application_loop.sh в фоновом режиме для заданного диапазона портов:
for PORT in `seq 8000 8002`; do /var/projects/my_application/bin/my_application_loop.sh $PORT 10 & done
И, наконец, последний — останавливающий — скрипт:
cd /var/projects/my_application killall -9 ea_my_application_loop.sh for PORT in `seq 8000 8002`; do PIDFILE="logs/$PORT.pid" if [ -f $PIDFILE ]; then PID=`cat $PIDFILE` kill $PID; fi done
Скрипты для Windows
Во всех вышеприведённых скриптах мы использовали bash и предполагали, что события разворачиваются под UNIX/Linux.
Для Windows не составляет особого труда сделать всё по аналогии, применив в качестве скриптового языка сам Perl. Удобно, к примеру, воспользоваться сборкой wperl.exe, которая запускает процесс без видимого окна и, таким образом, снимает необходимость наблюдать лишние консольные окна или рыть на стороне какие-нибудь хитрые утилиты.
Можно (наверное) прописать наш back-end как множество Windows-сервисов средствами Perl-модулей, но дело это крайне муторное. К примеру, если базироваться на дистрибутиве ActivePerl 5.8, то легко убедиться, что Win32::Daemon::Simple вроде бы устанавливается командой ppm, но имеет недокументированные зависимости, в частности, от Win32::Daemon, который уже доступен лишь в стороннем репозитории. И это только начало неприятностей.
В общем, на сегодняшний день апробирована кустарная, топорная, но понятная и надёжная схема управления процессами при помощи простых скриптов. Их автозапуск пока сочтём отдельной задачей. А исходные тексты не приведены здесь, поскольку имеет место...
Автоматизация процесса
Все необходимые скрипты и конфигурационные файлы генерируются по команде
perl -MEludia::Install -e bin
в директории приложения. У Вас запросят параметры (MaxRequestsPerChild и диапазон портов), после чего будет сформирован необходимый пакет файлов "под ключ". Если ядро Eludia.pm установлено где-то вне стандартного site/lib, не забудьте указать путь к нему в опции -I. Во все генерируемые файлы этот путь будет добавлен автоматически.
Параметры запрашиваются и файлы генерируются для 2 параллельных конфигураций: sea и sky, сразу на тот случай, если Вы решите организовать перезапуск WEB-сервера с нулевым временем простоя. Если нет, просто воспользуйтесь лишь одной из конфигураций.
Некоторые замечания
Разработка "на коленке" Perl WEB сервера, управляемого этажеркой скриптов, при живом mod_perl, вполне может показаться дикостью. Это бы так и было, если бы не ряд досадных обстоятельств:
- вывод из эксплуатации Apache 1 даже под Linux (в частности, отсутствует в родном Debian 5);
- однозадачность mod_perl 1 под Win32;
- многочисленные конфликты mod_perl 2 со сторонними XS-модулями, в особенности под Win32;
- отсутствие хорошего, стабильного (по крайней мере сопоставимого с mod_perl) многозадачного FastCGI-контейнера для Perl (в отличие от, скажем, PHP, поддерживающего PHP_FCGI_CHILDREN / PHP_FCGI_MAX_REQUESTS на уровне интерпретатора).
Поддержка фиксированного числа рабочих процессов также, возможно, на первый взгляд смотрится несуразно, однако практика показывает, что такая конфигурация ведёт себя гораздо более устойчиво и предсказуемо, чем схема с порождением дополнительных процессов по запросу. В реальности определяющим параметром настройки сервера является доступный объём памяти.
Далее, однозадачное программирование уже на уровне Perl-модулей, конечно, блокировало возможность экономии памяти за счёт загрузки модулей в shared memory, зато резко упростило отладку и ликвидировало привязку к платформе. Собственно, как показывают наблюдения за рабочими системами, даже под UNIX/Linux эффект от shared memory не особенно велик (порядка 10%) и абсолютно не прогнозируем, поскольку copy-on-write случаются, увы, весьма часто.
