[Перевод] Node.js и Express как они есть

Здравствуйте, любители нашего хаброблога и прочие читатели!

Мы планируем вновь отметиться на поле неувядающего Node.js и рассматриваем возможность издания этой книги:

136e4ee6dbdc42349dbe3cb097258944.jpg

Поскольку вполне понятен читательский интерес «а как он впихнул все это в двести страниц, и зачем мне это нужно»? под катом предлагаем перевод доскональной статьи Томислава Капана о том, зачем на самом деле нужен Node.js.

Введение

Благодаря растущей популярности, язык JavaScript в наше время активно развивается, и современная веб-разработка драматически изменилась по сравнению с недавним прошлым. Те вещи, которые мы сегодня можем делать в Вебе при помощи JavaScript, работающего на сервере, а также в браузере, было сложно себе вообразить еще несколько лет назад — в лучшем случае, такие возможности существовали только в песочницах вроде Flash или Java Applets.
Прежде чем подробно поговорить о Node.js, можете почитать о преимуществах полностекового использования JavaScript. При этом тесно переплетаются сам язык и формат данных (JSON), что позволяет оптимально переиспользовать ресурсы разработки. Поскольку это достоинство присуще не столько Node.js, сколько JavaScript в целом, мы не будем подробно останавливаться на этой теме. Но в этом и заключается ключевое преимущество, которое вы приобретаете, внедряя Node в свой стек.

В Википедии читаем: «Node.js — это пакет, включающий движок JavaScript V8 от Google, уровень абстракции платформы — библиотеку libuv, а также базовую библиотеку, которая сама написана на JavaScript.» Кроме того, необходимо отметить, что Райан Даль, автор Node.js, стремился создавать сайты, работающие в реальном времени и оснащенные push-функцией, под впечатлением от таких приложений как Gmail». В Node.js он предоставил программистам инструмент для работы с парадигмой неблокирующего, событийно-ориентированного ввода/вывода.

Спустя 20 лет господства парадигмы «веб-приложения без сохранения состояния на базе коммуникации запрос-отклик без сохранения состояния» у нас наконец-то есть приложения с двунаправленной связью в реальном времени.
Короче: Node.js блистает в приложениях реального времени, так как задействует push-технологию через веб-сокеты. Что же в этом такого революционного? Ну, как уже говорилось, спустя 20 лет использования вышеупомянутой парадигмы появились такие двунаправленные приложения, где связь может инициировать как клиент, так и сервер, а затем приступать к свободному обмену данными. Такая технология резко контрастирует с типичной парадигмой веб-откликов, где коммуникацию всегда инициирует клиент. Кроме того, вся технология основана на открытом веб-стеке (HTML, CSS и JS), работа идет через стандартный порт 80.

Читатель может возразить, что все это было у нас уже не один год — в виде Flash и Java-апплетов —, но на самом деле это были просто песочницы, использовавшие Веб в качестве транспортного протокола для доставки данных клиенту. Кроме того, они работали изолированно и зачастую действовали через нестандартные порты, что могло требовать дополнительных прав доступа и т.п.
Node.js при всех его достоинствах в настоящее время играет ключевую роль в технологическом стеке многих выдающихся компаний, непосредственно зависящих от уникальных свойств Node.

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

Основная идея Node.js: использовать неблокирующий событийно-ориентированный ввод/вывод, чтобы оставаться легковесным и эффективным при обращении с приложениями, обрабатывающими большие объемы данных в реальном времени и функционирующими на распределенных устройствах.

Пространно.

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

Тонкости работы Node.js «под капотом» довольно интересны. По сравнению с традиционными вариантами веб-сервисов, где каждое соединение (запрос) порождает новый поток, нагружая оперативную память системы и, в конце концов, разбирая эту память без остатка, Node.js гораздо экономичнее: он работает в единственном потоке, при вызовах использует неблокирующий ввод/вывод, позволяет поддерживать десятки тысяч конкурентных соединений (которые существуют в цикле событий).

a57d81149d874a7ab1dbde76c07a13ce.png

Простой расчет: допустим, каждый поток потенциально может затребовать 2 Мб памяти и работает в системе с 8 Гб оперативной памяти. В таком случае мы теоретически можем рассчитывать максимум на 4000 конкурентных соединений плюс издержки на переключение контекста между потоками. Именно с таким сценарием приходится иметь дело при использовании традиционных веб-сервисов. Node.js, избегая всего этого, может масштабироваться более чем до миллиона конкурентных соединений (в качестве эксперимента для подтверждения концепции).

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

Чтобы избежать всплывания исключений до самой поверхности применяется следующий прием: ошибки передаются обратно вызывающей стороне как параметры обратного вызова (а не выбрасываются, как в других средах). На случай, если какое-то необработанное исключение проскочит и всплывет, существует множество парадигм и инструментов, позволяющих следить за процессом Node и выполнять необходимое восстановление аварийно завершившегося экземпляра (хотя, пользовательские сеансы при этом восстановить не удастся). Наиболее распространенными из них являются модуль Forever, либо работа с применением внешних системных инструментов upstart и monit.

NPM: Менеджер пакетов Node

Обсуждая Node.js, просто необходимо упомянуть существующую в нем встроенную поддержку управления пакетами, для которой применяется инструмент NPM, по умолчанию присутствующий в любой установке Node.js. Идея модулей NPM во многом схожа с Ruby Gems: это набор общедоступных компонентов для многократного использования, которые легко установить через онлайновый репозиторий; для них поддерживается управление версиями и зависимостями.

Полный список упакованных модулей находится на сайте NPM npmjs.org, а также доступен при помощи инструмента NPM CLI, который автоматически устанавливается вместе с Node.js. Экосистема модулей совершенно открыта, любой может опубликовать в ней собственный модуль, который появится в списке репозитория NPM. Краткое введение в NPM (немного староватое, но по-прежнему актуальное) находится на сайте howtonode.org/introduction-to-npm.
Некоторые из наиболее популярных современных NPM-модулей:

  • express: Express.js, фреймворк для веб-разработки для Node.js, написанный в духе Sinatra, де-факто — стандартный для большинства существующих сегодня приложений Node.js.
  • connect: Connect — это расширяемый фреймворк, работающий с Node.js в качестве HTTP-сервера, предоставляющий коллекцию высокопроизводительных «плагинов», известных под общим названием «связующее ПО» (middleware); служит основой для Express.
  • socket.io и sockjs — серверная часть двух наиболее распространенных сегодня веб-сокетных компонентов.
  • Jade — один из популярных шаблонизаторов, написанный в духе HAML, по умолчанию используется в Express.js.
  • mongo и mongojs — обертки MongoDB, предоставляющие API для объектных баз данных MongoDB в Node.js.
  • redis — клиентская библиотека Redis.
  • coffee-script — компилятор CoffeeScript, позволяющий разработчикам писать программы с Node.js при помощи Coffee.
  • underscore (lodash, lazy) — Самая популярная вспомогательная библиотека JavaScript, упакованная для использования с Node.js, а также две аналогичные ей библиотеки, обеспечивающие повышенную производительность, поскольку реализованы немного иначе.
  • forever — Вероятно, самая распространенная утилита, обеспечивающая бесперебойное выполнение сценария на заданном узле. Поддерживает работоспособность вашего процесса Node.js при любых неожиданных сбоях.

Список можно продолжать. Существует множество общедоступных полезных пакетов, перечислить их все здесь просто невозможно.

В каких случаях следует использовать Node.js

ЧАТ

Чат — это наиболее типичное многопользовательское приложение, работающее в реальном времени. От IRC (были времена), использующих разнообразные открытые и открытые протоколы, функционирующие через нестандартные порты, мы пришли к современности, когда все можно реализовать на Node.js с применением веб-сокетов, действующих через стандартный порт 80.

Программа-чат — действительно идеальный продукт для использования Node.js: это легковесное приложение с высоким трафиком, интенсивно обрабатывающее данные (однако почти не потребляющее вычислительных мощностей), работающее на множестве распределенных устройств. Кроме того, на нем очень удобно поучиться, так как при всей простоте оно охватывает большинство парадигм, которые может потребоваться использовать в типичном приложении Node.js.

Попробуем изобразить, как оно работает.

В простейшем случае у нас на сайте единственная комната для чата, куда пользователи приходят и обмениваются сообщениями в режиме «один к многим» (фактически — «ко всем»). Допустим, у нас на сайте три посетителя, и все они могут писать сообщения на нашем форуме.

На стороне сервера у нас простое приложение Express.js, в котором реализуются две вещи: 1) обработчик запросов GET »/», обслуживающий веб-страницу, на которой располагается как форум с сообщениями, так и кнопка «Send», инициализирующая новое введенное сообщение и 2) сервер веб-сокетов, слушающий новые сообщения, выдаваемые клиентами веб-сокетов.

На стороне клиента у нас есть HTML-страница, на которой настроено два обработчика: один из них слушает события щелчка по кнопке «Send», которая подхватывает введенное сообщение и спускает его на веб-сокет, а другой слушает новые входящие сообщения на клиенте для обслуживания веб-сокетов (т.е., сообщения, отправленные другими клиентами, которые сервер собирается отобразить при помощи этого специального клиента).

Вот что происходит, когда один из клиентов отправляет сообщение:

  1. 1. Браузер подхватывает щелчок по кнопке «Send» при помощи JavaScript-обработчика, забирает значение из поля ввода (т.e., текст сообщения) и выдает сообщение веб-сокета, воспользовавшись клиентом веб-сокетов, подключенным к нашему серверу (этот клиент инициализируется вместе с веб-страницей).
  2. 2. Серверный компонент веб-сокетного соединения получает сообщение и перенаправляет его всем остальным подключенным клиентам широковещательным методом.
  3. 3. Все клиенты получают новое сообщение по принципу push при помощи веб-сокетного компонента, выполняющегося на веб-странице. Затем они подхватывают контент сообщения и обновляют веб-страницу, добавляя на форум новую запись.

7c501bc662e0427b907bc94b4a595346.png

Это простейший пример. Для более надежного решения можно использовать простой кэш, основанный на хранилище Redis. Еще более продвинутое решение — очередь сообщений, позволяющая обрабатывать маршрутизацию сообщений к клиентам и обеспечивающая более надежный механизм доставки, который позволяет компенсировать временные обрывы соединения или хранить сообщения для зарегистрированных клиентов, пока они находятся в оффлайне. Но независимо от того, какие оптимизации вы выполняете, Node.js все равно будет действовать в соответствии с теми же базовыми принципами: реагировать на события, обрабатывать множество конкурентных соединений, поддерживать плавность пользовательских взаимодействий.

API ПОВЕРХ ОБЪЕКТНОЙ БАЗЫ ДАННЫХ

Хотя Node.js особенно хорош в контексте приложений, работающих в реальном времени, он вполне подходит и для предоставления информации из объектных баз данных (напр. MongoDB). Данные, сохраненные в формате JSON, позволяют Node.js функционировать без потери соответствия и без преобразования данных.

Например, если вы используете Rails, то вам пришлось бы преобразовывать JSON в двоичные модели, а потом снова предоставлять их в виде JSON по HTTP, когда данные будут потребляться Backbone.js, Angular.js или даже обычными вызовами jQuery AJAX. Работая с Node.js, можно просто предоставлять ваши объекты JSON клиенту через REST API, чтобы клиент их потреблял. Кроме того, не приходится беспокоиться о преобразовании между JSON и чем угодно еще при считывании базы данных и записи в нее (если вы используете MongoDB). Итак, вы обходитесь без множества преобразований, используя универсальный формат сериализации данных, применяемый и на клиенте, и на сервере, и в базе данных.

ОЧЕРЕДИ ВВОДА

Если вы получаете большие объемы конкурентных данных, то база данных может стать узким местом. Как показано выше, Node.js с легкостью обрабатывает конкурентные соединения как таковые. Но поскольку обращение к базе данных является блокирующей операцией (в данном случае), у нас возникают проблемы. Решение в том, чтобы зафиксировать поведение клиента перед тем, как данные на самом деле будут записаны в базу.

При использовании такого подхода отзывчивость системы сохраняется и под высокой нагрузкой, что особенно полезно, если клиенту не требуется подтверждение о том, что запись данных прошла успешно. Типичные примеры: логирование или запись данных о пользовательской активности (user tracking), обрабатываемых по пакетному принципу и не используемых впоследствии; операции, итог которых должен отражаться мгновенно (например, обновление количества «лайков» в Facebook), где приемлема согласованность в конечном счете, так часто используемая в мире NoSQL.

Данные выстраиваются в очередь при помощи специальной инфраструктуры для кэширования и работы с очередями сообщений (напр., RabbitMQ, ZeroMQ) и перевариваются отдельным процессом базы данных, предназначенным для пакетной записи, либо специальными сервисами интерфейса базы данных, рассчитанных на интенсивные вычисления. Аналогичное поведение можно реализовать и при помощи других языков/фреймворков, но на ином аппаратном обеспечении и не с такой высокой и стабильной пропускной способностью.

79ad99ee97fc4edf8fd82994356b94ee.png

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

ПОТОКОВАЯ ПЕРЕДАЧА ДАННЫХ

На более традиционных веб-платформах HTTP-запросы и отклики трактуются как изолированные события;, но фактически они представляют собой потоки. Этим моментом можно воспользоваться в Node.js для создания некоторых классных возможностей. Например, можно обрабатывать файлы, пока они еще закачиваются, поскольку данные поступают потоком, и мы можем работать с ними в онлайновом режиме. Это можно сделать, например, при кодировании видео или аудио в реальном времени, а также при установке прокси между различными источниками данных (подробнее см. в следующем разделе).

ПРОКСИ

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

Хотя и существуют выделенные прокси-серверы, вместо них удобно использовать Node, особенно если прокси-инфраструктуры не существует, либо если вам нужно решение для локальной разработки. Здесь я имею в виду, что можно создать клиентское приложение, где будет применяться сервер разработки Node.js, где мы будем хранить ресурсы и делать прокси/заглушки для запросов к API, а в реальных условиях такие взаимодействия уже будут выполняться при помощи выделенного прокси-сервиса (nginx, HAProxy, т.д.)

ИНФОРМАЦИОННАЯ ПАНЕЛЬ БИРЖЕВОГО ТРЕЙДЕРА

Вернемся на уровень приложений. Еще один сегмент, где доминируют программы для ПК, но их можно с легкостью заменить веб-решением, работающим в реальном времени — это софт для биржевого трейдинга, где отслеживаются котировки, выполняются вычисления и технический анализ, вычерчиваются графики и диаграммы.
Если задействовать в таком случае веб-решение для работы в реальном времени, то пользующийся им брокер сможет легко переключаться между рабочими станциями или местами. Вскоре мы начнем замечать таких брокеров на пляжах Флориды… Ибицы… Бали.

ПАНЕЛЬ ДЛЯ МОНИТОРИНГА ПРИЛОЖЕНИЙ

Это еще один практический случай, для которого идеально подходит модель «Node+веб-сокеты». Здесь мы отслеживаем посетителей сайта и визуализируем их взаимодействия в реальном времени (если вас интересует такая идея, то она уже решается при помощи Hummingbird).

Можно собирать статистику о пользователе в реальном времени и даже перейти на более высокий уровень, дополнив программу целевыми взаимодействиями с посетителем, открывая канал связи, как только гость достигнет конкретной точки в вашей воронке (если вас интересует такая идея, то она уже решается при помощи CANDDi).

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

ИНФОРМАЦИОННАЯ ПАНЕЛЬ ДЛЯ ОТСЛЕЖИВАНИЯ СИСТЕМЫ

Теперь давайте поговорим об инфраструктурных аспектах. Допустим, есть SaaS-провайдер, желающий предложить пользователям страницу для отслеживания сервисов (скажем, статусную страницу GitHub). Имея цикл событий Node.js, можно создать мощный веб-интерфейс, где состояния сервисов будут асинхронно проверяться в реальном времени, а данные будут отправляться клиенту через веб-сокеты.

Такая технология позволяет сообщать о статусах как внутренних (внутрикорпоративных), так и общедоступных сервисов в реальном времени. Давайте немного разовьем эту идею и попытаемся представить сетевой операционный центр (NOC), отслеживающий работу приложений оператора связи, провайдера облачных сервисов/хостинг-провайдера или какой-нибудь финансовой организации. Все это работает в открытом веб-стеке на основе Node.js и веб-сокетов, а не Java и/или Java-апплетов.

Примечание: Не пытайтесь создавать на Node жесткие системы реального времени (т.e., требующие четко определенного времени отклика). Пожалуй, подобные приложения лучше разрабатывать на Erlang.

Где можно использовать Node.js

СЕРВЕРНЫЕ ВЕБ-ПРИЛОЖЕНИЯ

Node.js c Express.js также можно применять для создания классических веб-приложений на серверной стороне. Однако, пусть это и возможно, такая парадигма запрос/отклик, где Node.js будет переносить отображенный HTML, нетипична для данной технологии. Существуют аргументы как в пользу такого подхода, так и против него. Необходимо учитывать следующее:

За:

  • Если ваше приложение не выполняет интенсивных вычислений, нагружающих процессор, то его можно написать полностью на JavaScript, включая даже базу данных, если вы используете объектную базу данных (например, MongoDB) и JSON. Это значительно упрощает не только разработку, но и подбор специалистов.
  • Поисковые роботы получают в ответ полностью отображенный HTML, что гораздо удобнее для поисковой оптимизации, чем, например, работа с одностраничными приложениями или веб-сокетным приложением, работающим на базе Node.js.
  • Любые интенсивные вычисления, нагружающие процессор, будут блокировать динамичность Node.js, поэтому в таком случае лучше использовать многопоточную платформу. Также можно попробовать горизонтальное масштабирование вычислений [*].
  • Использовать Node.js с реляционной базой данных по-прежнему довольно неудобно (подробнее см. ниже). Сделайте себе одолжение и выберите какую-нибудь другую среду, например, Rails, Django или ASP.Net MVC, если собираетесь заниматься реляционными операциями.

[*] В качестве альтернативы таким CPU-интенсивным вычислениям можно создать хорошо масштабируемую MQ-среду с обработкой на интерфейсе базы данных, чтобы Node оставался «на передовой» и асинхронно обрабатывал клиентские запросы.

В каких случаях не следует использовать Node.js

СЕРВЕРНОЕ ВЕБ-ПРИЛОЖЕНИЕ, ЗА КОТОРЫМ РАСПОЛОЖЕНА РЕЛЯЦИОННАЯ БАЗА ДАННЫХ

Сравнивая Node.js плюс Express.js с Ruby on Rails, мы уверенно выбираем второй вариант, если речь идет о доступе к реляционным данным.

Инструменты реляционных баз данных для Node.js по-прежнему находятся в зачаточном состоянии, работать с ними довольно неприятно. С другой стороны, Rails автомагически настраивает доступ к данным прямо при установке, плюс предоставляет инструменты для поддержки миграций схем баз данных и прочие Gems. Rails и соответствующие фреймворки обладают зрелыми и проверенными реализациями доступа к уровню данных (Active Record или Data Mapper), которых вам будет остро недоставать, если вы попытаетесь воспроизвести такую конструкцию на чистом JavaScript.[*]

Все-таки, если вы твердо намерены все делать на JavaScript, обратите внимание на Sequelize и Node ORM2— оба инструмента пока не лишены шероховатостей, но со временем могут и дозреть.

[*] Можно использовать Node исключительно в клиентской части (нередко так и делается), а машинный интерфейс выполнить на Rails, сохранив, таким образом, легкий доступ к реляционной базе данных.

СЛОЖНЫЕ СЕРВЕРНЫЕ ВЫЧИСЛЕНИЯ И ОБРАБОТКА

Когда речь заходит о сложных вычислениях, Node.js оставляет желать лучшего. Разумеется, вы не собираетесь программировать на Node сервер для вычислений Фибоначчи. В принципе, любые вычислительные операции, сильно нагружающие процессор, девальвируют выигрыш в пропускной способности, который в Node достигается благодаря событийно-ориентированному неблокирующему вводу/выводу. Дело в том, что любые входящие запросы будут блокироваться, пока единственный поток занят перевариванием чисел.

Как было указано выше, Node.js однопоточный и использует всего одно ядро процессора. Может потребоваться реализовать конкурентность на многоядерном сервере, для этого команда разработчиков ядра Node уже занимается подготовкой специального кластерного модуля. Кроме того, вы без труда могли бы запустить несколько экземпляров сервера Node.js за обратным прокси с использованием nginx.

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

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

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

Заключение

Мы обсудили Node.js с теоретической и практической точки зрения, начав с его целей и назначения и закончив разговором о всяческих вкусностях и подводных камнях. Если у вас возникнут проблемы с Node.js, то помните, что почти всегда они сводятся к следующему факту: блокирующие операции — причина всех бед. В 99% процентах случаев все проблемы начинаются из-за неправильного использования Node.

Помните: Node.js никогда не предназначался для решения проблемы масштабирования вычислений. Он создавался для масштабирования ввода/вывода, с чем действительно справляется очень хорошо.

Зачем использовать Node.js? Если стоящая перед вами задача не предполагает интенсивных вычислений и обращения к блокирующим ресурсам, то можно в полной мере воспользоваться преимуществами Node.js и наслаждаться быстрыми и легко масштабируемыми веб-приложениями.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

© Habrahabr.ru