Подводные камни при разработке крупных приложений на Node.js

This post is also available in: Английский

Уже стало понятно, что Node.js — яркий тренд в сообществе web-разработчиков. В данной статье мы попытаемся затронуть следующие вопросы: какие подводные камни могут ожидать разработчика, и стоит ли вообще переходить на нее.

В основном Node.js ассоциируется у разработчиков с небольшими сервисами народе чата. (подробнее в статье «Разработка высокопроизводительных сервисов на Node.js»). На то есть веские причины. Нода отлично подходит для сервисов, которые поддерживают постоянное подключение к клиенту, либо долгое время ожидают ответа от него. Отсутствие блокировки ввода/вывода позволит читать большие файлы, оставляя при этом ваш сервис готовым к обработке новых запросов.

Итак, вот за что сообщество так любит Node.js:

• Нода может держать тысячи открытых соединений с клиентами, параллельно с этим осуществляя запись в БД.
• Событийная модель работы отлично подходит для каскадов асинхронных запросов к другим частям приложения.
• Большое и отзывчивое сообщество, а также быстрая скорость разработки самой технологии.
• Множество отличных модулей.
• JSON — родной формат описания объектов. А при использовании MongoDB вообще отпадает вопрос сериализации.

Кстати, нода позволяет также быстро реализовать и высоконагруженные REST-сервисы. А такие модули как express существенно упрощают эту задачу.

Тем не менее у каждой медали есть и обратная сторона.

Неблокиремый поток?

Мнение, что node.js невозможно заблокировать, ошибочно. Архитектурно нода устроена таким образом, что все выполняется в единственном потоке. Соответственно тяжелые математические вычисления спокойно повесят приложение на какое-то время.

Как же с этим бороться?  Существует несколько подходов к решению данной проблемы. Все они основаны на идее вынесения тяжелые вычисления из основного потока исполнения (Control Flow). Итак, вот они:

  1. Разбить выполнение кода на итерации и выполнять их через функцию setTimeout с задержкой в 0 мс. Стоит понимать, что 0 абсолютно не значит, что задержка будет отсутствовать. Это означает, что нода начнет исполнять код так скоро, как сможет. Конечно, придется где-то сохранять промежуточные результаты. Также стоит упомянуть и об увеличении длительности выполнения этих самых вычислений.
  2. Вынести исполнение в воркеры. По сути такой подход породит дополнительные процессы в системе, в которых и выполнится наш код.

Чтение/запись в файлы и возникновение коллизий

В случае, если вы оперируете содержимым больших файлов между воркерами, то нужно помнить об особенностях доступа к первым. При открытии файла нода получит от системы его дескриптор. В воркеры будет необходимо его передавать

Также часто возникает ситуация, когда нужно уведомить воркеры и другие инстансы ноды о том, что c файлом проводятся манипуляции. Фактически нужно заблокировать файл для других процессов, дабы запретить параллельную запись в него. Для таких целей пишется отдельная нода-контроллер, которая будет выдавать другим разрешение на чтение/запись и составлять очередь ожидания на запись.

Стоит заметить, что такой подход справедлив не только для работы с файлами, но и для любых задач, в которой необходимо обеспечить контроль за коллизиями.

Блокировка исполнения при работе Garbage Collector.

Как известно, Node.js  построена на базе движка V8. Его особенность заключается в том, что при запуске GC нода останавливается до окончания его работы. Время задержки напрямую зависит от числа объектов в куче. «Текущие» приложения теряют в производительности именно из-за работы garbage collector. Результаты работы сборщика мусора можно вывести, запустив ноду следующим образом:

node <script> —trace-gc

Существует подход, когда приложение разделяют на несколько частей с целью уменьшения числа объектов в пределах одного модуля системы. Тогда работа сборщика мусора в каждом из них будет занимать меньшее время. Идея правильная, однако стоит помнить, что работу асинхронного кода модулей нужно согласовывать, создавая сложные конструкции.

Таким образом, аккуратное и вдумчивое создание объектов должно стать вашей основной парадигмой при написании сервисов на Node.js.

Подводные камни при отладке.

Отладка ноды — непростая задача. Конечно, можно просто запустить ноду с в debug-режиме. Но удобство отладки приложения в нем довольно низкое. Гораздо проще использовать сторонние приложения:

1) node-inspector

Этот продукт позволяет удаленно отлаживать ноду в интерфейсе WebInspector, редактировать runtime-код и даже профилировать приложение.

Ниже небольшое видео про отладку ноды при помощи node-inspector

2)  PHPStorm/WebStorm Node.js Debugger

Данные редакторы имеют встроенный функционал для локальной и удаленной отладки ноды в своей собственной среде.

Событийная модель, заложенная в Node.js может легко запутать разработчика. Вывод стека, состоящего из последовательных вызовов callback-функций как правило абсолютно не информативен. Тем не менее можно повысить читаемость вывода если принять за правило все коллбекам давать имена. Например:

1
2
3
setTimeout(function do() {
  /* Some code here */
});

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

Каскады callback-функций

Сложные приложения на Node.js подразумевают последовательный вызовов множества коллбеков. Часто в коде можно увидеть следующие конструкции:

1
2
3
4
5
6
7
8
9
do(function () {
  ...  
  function() {
    ...
    function() {
      ...
    }
  }
});

Читать и поддерживать такой код крайне сложно. Для организации кода при таких ситуациях используются специальные паттерны. Подробнее о них можно прочитать в статье «Node.js Control flow».

Изменчивое API

Нода и ее модули быстро меняются. Потому существуют риски, что новые версии как самой Node.js, так и ее модулей сломают работу всего приложения. Нужно быть готовым к тому, что переход на новые версию означает изменение реализации целых блоков сервиса.

По этой самой причине мы рекомендуем не ставить ни один модуль глобально. Вполне вероятна ситуация, когда 2 разных ноды на одной машине используют 2 несовместимые между собой версии одного и того же модуля.

npm install <module>

Примеры успешного применения Node.js

На GitHub расположен целый список компаний, которые используют ноду в своих сервисах. ознакомится с ним можно по этой ссылке.

Мы остановимся на некоторых из них:

  • Yahoo реализовали на ноде фреймворк Mojito, позволяющий писать приложения, работающие как на клиентской, так на серверной стороне, в зависимости от того, какое устройство из запускает.
  • Yammer использует сервис на Node.js для кросс-доменного проксирования запросов к API. В качестве основного преимущество разработчики из Yammer приводят возможность ноды обслуживать множество одновременных запросов.
  • Bocoup написали на ноде IRC_бота. Выбор в пользу этой технологии был сделан исходя идеи использования JavaScript на стороне сервера (У Bocoup много JS-разработчиков)

Резюме

Использовать или не использовать Node.js — решать только вам, исходя из специфики конкретной задачи. Но стоит понимать, что у нее есть и сильные, и слабые стороны.

Полезные ссылки