Nginx
29 January 2010

Nginx + серверный Javascript

… или как перейти с PHP + JavaScript на JavaScript + JavaScript


Идея реализовать проект на сервер-сайд JavaScript была уже давно. Проблема была в отсутствии подходящего серверного программного обеспечения. Существующие открытые проекты не устраивали по разным причинам. Устанавливать дополнительный модуль для Apache было не самой хорошей идеей, потому что производительность и оптимизация использования памяти при этом были бы не на высоте. С помощью jslibs можно настроить FastCGI, но очень не хотелось оставлять ни малейших шансов «502 Bad Gateway», проект ngx_http_js_module так и остался в зачаточной стадии, а ngxv8 недостаточно развит для реализации реальных приложений. Поэтому я решил сделать собственную реализацию серверного javascript. Причем постараться сразу запрограммировать всю базовую функциональность, чтобы можно было ее тестировать в условиях, близких к реальности.

В качестве основного веб-сервера было решено использовать nginx, в качестве «движка» javascript — TraceMonkey (javascript-движок из Mozilla Firefox, бывший SpiderMonkey), и написать модуль для nginx, который бы их «склеил». Ничего сложного, на первый взгляд, но очень хотелось иметь определенную функциональность (и это получилось!), чтобы можно было нормально работать дальше. Большинство идей заимствованы, кстати, из PHP.
  • Корректная работа в multi-thread условиях
  • Возможность выполнять скрипт, указанный в URL, а не настраивать отдельно скрипт-обработчик и функцию-обработчик для каждого location
  • Возможность вызывать include(), sleep(), alert() из скрипта, использовать __FILE__ и __LINE__
  • Ограничение памяти, выделяемой каждому скрипту, и времени работы скрипта
  • Защита открываемых скриптом файлов, указав в настройках список разрешенных папок. Примерно как open_basedir в PHP
  • Автоматический разбор данных запроса (параметров GET, POST, и, конечно же, cookies), чтобы не писать обработку данных на javascript
  • Поддержка запросов application/x-www-form-urlencoded и multipart/form-data
  • Поддержка basic-авторизации
  • Работа с базами данных (в первую очередь, MySQL и SQLite)
  • Работа с файловой системой: чтение и запись файлов, проверка существования файлов, и т.п.
  • Кэширование байт-кода скриптов, как, например, в eAccelerator
Плюс некоторые другие возможности (инструменты для шаблонизации, для создания конфигурационных файлов, и т.п.), но их в основной список я не включил — их позволяют сделать языковые возможности TraceMonkey.

От слов — к делу! Как скомпилировать и настроить, как протестировать и сравнить...

Глубоко в детали сборки не вдаюсь, иначе текст получится неимоверных размеров. Пользователи, имеющие опыт «сборки» программ под Linux, будут чувствовать себя вполне комфортно, а для всех остальных могу предложить бинарную сборку и возможность пропустить процесс самостоятельной компиляции.

Понадобятся:
  • Linux
  • Компиляторы C и C++, autoconf 2.13
  • Исходники nginx
  • TraceMonkey из репозитория
  • Библиотека NSPR
  • Наш модуль
  • MySQL и SQLite (опционально) + средства разработки
Порядок сборки следующий.

Сначала NSPR последней версии (на момент написания — 4.8.2):

wget ftp://ftp.mozilla.org/pub/mozilla.org/nspr/releases/v4.8.2/src/nspr-4.8.2.tar.gz<br/>tar -xzf nspr-4.8.2.tar.gz<br/>cd nspr-4.8.2/mozilla/nsprpub<br/>./configure --prefix=/usr/local --with-pthreads<br/>make<br/>sudo make install<br/>
Затем TraceMonkey из репозитория (на момент написания, в репозитории версия 1.8.5, а скачать файл с исходниками можно только для 1.7.0):

hg clone http://hg.mozilla.org/tracemonkey/<br/>cd tracemonkey/js/src<br/>autoconf2.13<br/>./configure --prefix=/usr/local --with-nspr-prefix=/usr/local --with-system-nspr --with-pthreads --enable-threadsafe<br/>make<br/>sudo make install<br/>
Этот шаг может быть проблемным по нескольким причинам. Во-первых, не у всех есть команда hg. А во-вторых, из репозитория скачиваются все исходники Mozilla Firefox. Поэтому, первую строчку кода можно заменить и скачать исходники только TraceMonkey:

# hg clone http://hg.mozilla.org/tracemonkey/<br/>wget http://js.nnov.ru/files/tracemonkey-20100119.tar.gz<br/>tar -xzf tracemonkey-20100119.tar.gz<br/>
И затем уже скомпилировать.

Далее nginx (0.8.32) и модуль javascript:

wget http://sysoev.ru/nginx/nginx-0.8.32.tar.gz<br/>tar -xzf nginx-0.8.32.tar.gz<br/>cd nginx-0.8.32/src/http/modules<br/>svn co http://nginx-javascript.googlecode.com/svn/trunk/ javascript<br/>cd ../../..<br/>./configure --prefix=/usr/local/nginx-javascript --add-module=src/http/modules/javascript<br/>make<br/>sudo make install<br/>
Если все получилось — то переходим к настройке. Счастливые обладатели бинарной сборки обнаружат, что конфигурация уже выполнена, но лишний раз проверить не помешает. Достаточно выполнить следующие шаги:
  • Добавить в mime.types тип application/x-javascript-serverside для файлов, которые будут обрабатываться как javascript:
    # /usr/local/nginx-javascript/conf/mime.types<br/>types {<br/>    ...<br/>    application/x-javascript-serverside jsx;<br/>    ...<br/>}<br/>
    Расширение .jsx выбрано вместо стандартного .js, чтобы сервер не обрабатывал обычные java-скрипты как серверные
  • Разрешить в разделе location / файла nginx.conf обработку javascript. Заодно сменим номер порта, на котором будет работать сервер:
    # /usr/local/nginx-javascript/conf/nginx.conf<br/>...<br/>    server {<br/>        listen 8081;<br/>        ...<br/>        location / {<br/>            ...<br/>            javascript on;<br/>            ...<br/>        }<br/>    }<br/>...<br/>
  • Запустить nginx:
    /usr/local/nginx-javascript/sbin/nginx<br/>
  • Создать тестовый скрипт hello.jsx:
    // /usr/local/nginx-javascript/html/hello.jsx<br/>print("Hello, people!");<br/>
  • Проверить, что hello.jsx выглядит в браузере как надо:
    curl http://localhost:8081/hello.jsx<br/>
Добившись того, что сервер с javascript заработал, мне стало интересно, насколько выгоднее такое решение, чем стандартное Apache + PHP. Так как вопросы внутренней оптимизации TraceMonkey и PHP меня волновали несколько меньше (например, какой интерпретатор быстрее выполняет цикл из миллиона шагов? Подозреваю, что разница небольшая), то тестировался, в первую очередь, скрипт «Hello, people!».

В сравнении участвовали:
  • Apache/2.2.14 (prefork) + PHP/5.2.12 (модуль)
  • nginx/0.8.32 (1 рабочий процесс) + javascript
  • nginx/0.8.32 (8 рабочих процессов) + javascript
Среда тестирования — 4-ядерный Xeon с 2ГБ оперативной памяти и Debian Etch. Весь трафик локальный. В подробности «железа» не вдаюсь, в детали конфигурации тоже — настройки более или менее стандартные.

Сначала цикл тестов из 1000 запросов один за другим:

# Apache 2.2.14 (prefork) + PHP 5.2.12 (module)<br/>ab -n 1000 http://localhost:8085/hello.php<br/>Time per request: 5.278 [ms] (mean, across all concurrent requests)<br/># nginx (1 worker) + javascript<br/>ab -n 1000 http://localhost:8081/hello.jsx<br/>Time per request: 1.298 [ms] (mean, across all concurrent requests)<br/># nginx (8 workers) + javascript<br/>ab -n 1000 http://localhost:8088/hello.jsx<br/>Time per request: 1.322 [ms] (mean, across all concurrent requests)<br/>
Теперь цикл тестов из 1000 запросов при создании 100 одновременных подключений:

# Apache 2.2 (prefork) + PHP 5.2 (module)<br/>ab -n 1000 -c 100 http://localhost:8085/hello.php<br/>Time per request: 1.648 [ms] (mean, across all concurrent requests)<br/># nginx (1 worker) + javascript<br/>ab -n 1000 -c 100 http://localhost:8081/hello.jsx<br/>Time per request: 1.277 [ms] (mean, across all concurrent requests)<br/># nginx (8 workers) + javascript<br/>ab -n 1000 -c 100 http://localhost:8088/hello.jsx<br/>Time per request: 0.544 [ms] (mean, across all concurrent requests)<br/>
Выводы из тестирования:
  • Если запросы к серверу идут последовательно, один за другим, nginx+javascript работает значительно быстрее (у нас примерно в 3 раза). При этом nginx с одним рабочим процессом работает даже чуть-чуть быстрее. В реальности такая ситуация практически никогда не происходит: чаще много клиентов открывают одновременно разные страницы
  • Если запросы к серверу отправляются одновременно, скорость работы apache+php увеличивается (у нас они показали почти такую же скорость, как nginx+javascript с одним рабочим процессом). Но и скорость работы nginx+javascript с несколькими рабочими процессами тоже возрастает (у нас — более, чем в 2 раза). А nginx+javascript с одним рабочим процессом осталась практически неизменной
Кроме того, что такая реализация серверного javascript позволяет добиться увеличения производительности по сравнению с традиционным PHP, javascript позволяет использовать достаточно хитрые языковые конструкции.

// Выводит параметры id запросов GET, POST и cookies:<br/>print($request.get['id'], " ", $request.post['id'], " ", $request.cookie['id']);<br/>// Отправляет заголовок Content-Type:<br/>$result.headers.push("Content-Type: text/html; charset=UTF-8");<br/>// Открывает базу данных, выполняет запрос SELECT с параметром, переданным в GET, и забирает одну строку результата:<br/>var row = (new SQLite("database")).query("SELECT * FROM `table` WHERE `id`=?", $request.get['id']).fetch();<br/>// Читает файл:<br/>print(File.open("index.html").getChars());<br/>// Выводит IP-адрес клиента, открывашего страницу:<br/>print({$server.remoteAddr});<br/>
В последнем примере нет ошибки синтаксиса, XML-документы действительно можно использовать внутри скриптов. При этом в них можно вставлять обращения к переменным и вызовы функций, заключая их в фигурные скобки. Эту технологию, E4X, очень удобно применять для создания шаблонов. Еще ряд примеров можно найти на http://js.nnov.ru/nginx/examples.html.

Конечно, есть еще ряд проблем, которые нужно постепенно решить:
  • Поддержка загрузки файлов (coming soon!)
  • Поддержка cURL и GD, без которых очень сложно жить
  • Оптимизация системных вызовов stat(), которые сейчас используются для определения определения реального пути к файлу
Но, в общем и целом, можно пользоваться. Кстати, небольшой сайт http://js.nnov.ru сделан на JavaScript. Просьба жестких тестов на отказоустойчивость пока не проводить :-)

З.Ы.> Отдельное спасибо FTM за инвайт, благодаря которому топик уже не в песочнице
UPD> Сразу бы опубликовал в тематическом, но были проблемы с кармой. Спасибо всем участвующим!

+112
8.2k 109
Comments 95
Top of the day