Лучшие практики: производительность и надежность¶
Обзор¶
В статье рассматриваются лучшие практические методы обеспечения производительности и надежности приложений Express, развернутых в рабочей среде.
Рассматриваемая тема, без сомнения, относится к категории "DevOps", которая рассматривает процесс традиционной разработки программного обеспечения во взаимосвязи с эксплуатацией. Соответственно, представленную в ней информацию можно разделить на две части:
Что можно сделать в коде¶
Ниже приведены некоторые примеры того, что можно сделать в коде для улучшения производительности приложений.
- Использовать сжатие gzip
- Не использовать синхронные функции
- Использовать промежуточные обработчики для обслуживания статических файлов
- Организовать корректное ведение протоколов
- Правильно обрабатывать исключительные ситуации
Использовать сжатие gzip¶
Сжатие gzip может значительно уменьшить размер тела ответа и, соответственно, увеличить быстродействие веб-приложения. Используйте промежуточный обработчик для сжатия gzip в приложениях Express. Например:
1 2 3 4 |
|
Для активно используемого веб-сайта в рабочей среде разумнее всего реализовать сжатие на уровне обратного прокси. В этом случае можно обойтись без промежуточного обработчика для сжатия данных. Более подробная информация об активации сжатия в Nginx приведена в разделе Модуль сжатия ngx_http_gzip_module документации по Nginx.
Не использовать синхронные функции¶
Синхронные функции и методы задерживают выполнение процесса до возвращения ответа. Один вызов синхронной функции может возвращать значение через несколько микросекунд или миллисекунд, однако в активно используемых веб-сайтах эти вызовы дают суммарный эффект снижения производительности приложения. В рабочей среде от них лучше отказаться.
Модуль Node и многие другие модули поддерживают синхронную и асинхронную версию выполнения функций; однако в рабочей среде следует использовать только асинхронную версию. Синхронное выполнение функций может быть оправдано только при первоначальном запуске.
При работе с Node.js 4.0+ или io.js 2.1.0+ можно воспользоваться флагом командной строки --trace-sync-io
, который выводит предупреждение и трассировку стека, если в приложении используется синхронный API. В рабочей системе это, конечно, лишнее; скорее, это позволяет убедиться, что код готов для рабочей среды. Дополнительная информация приведена в разделе Еженедельное обновление io.js 2.1.0.
Использовать промежуточный обработчик для обслуживания статических файлов¶
В среде разработки для обслуживания статических файлов можно использовать метод res.sendFile()
. Для рабочей среды этот метод не подходит: при обработке каждого запроса файла он выполняет чтение из файловой системы, создавая большую задержку и снижая общую производительность приложения. Заметьте, что метод res.sendFile()
не реализован для системного вызова sendfile, который мог бы существенно повысить его эффективность.
Рекомендуем воспользоваться промежуточным обработчиком serve-static (или аналогичным ему), оптимизированным для обслуживания файлов приложений Express.
Еще лучше воспользоваться для обслуживания статических файлов обратным прокси; дополнительная информация приведена в разделе "Использование обратного прокси-сервера".
Организовать корректное ведение протоколов¶
В целом вести протоколы работы приложения необходимо по двум причинам: в целях отладки и в целях регистрации работы приложения (по сути, сюда относится все остальное). На этапе разработки, сообщения протокола обычно выводят на терминал при помощи console.log()
или console.err()
. Но в случае вывода на терминал или в файл эти функции выполняются синхронно, поэтому для рабочей среды они подойдут только при условии вывода в другую программу.
В целях отладки¶
При ведении протокола в целях отладки рекомендуется вместо console.log()
воспользоваться специальным отладочным модулем типа debug. При работе с этим модулем можно использовать переменную среды DEBUG, которая определяет, какие отладочные сообщения будут переданы в console.err()
. А для того чтобы приложения оставались чисто асинхронными, рекомендуется выводить результаты console.err()
в другую программу. Но вы же не собираетесь заниматься отладкой в рабочей среде, верно?
В целях регистрации работы приложения¶
Для регистрации работы приложения (например, учета переданных данных или отслеживания вызовов API-функций) можно вместо console.log()
воспользоваться библиотекой регистрации типа Winston или Bunyan. Подробное сравнение двух библиотек проведено в корпоративном блоге StrongLoop Сравнение протоколирования Node.js с использованием Winston и Bunyan.
Правильно обрабатывать исключительные ситуации¶
При наступлении необрабатываемой исключительной ситуации в приложениях Node происходит сбой. Если не обрабатывать исключительные ситуации и не принимать необходимых мер, это приведет к сбою и отключению приложения Express. Рекомендация из следующего раздела "Автоматический перезапуск приложения" поможет вам обеспечить восстановление приложения после сбоя. К счастью, приложения Express обычно имеют небольшое время запуска. Тем не менее, нужно позаботиться прежде всего о недопущении сбоев, для чего необходимо правильно обрабатывать исключительные ситуации.
Для того чтобы в системе обрабатывались все исключительные ситуации, можно воспользоваться следующими методами:
- метод
try-catch
- метод
Promise
Прежде чем перейти к этим темам, необходимо понимать основные принципы обработки ошибок в Node/Express: использование функции обратного вызова, в которой первый аргумент зарезервирован за объектом ошибки, и распространение ошибок в промежуточном обработчике. В Node принято соглашение "error-first callback" для возврата ошибок асинхронных функций: первый параметр любой функции обратного вызова всегда является объектом-ошибкой, за ним следуют параметры, содержащие результат обработки. Для того чтобы сообщить об ошибке, в качестве первого параметра передайте нуль. Для правильной обработки ошибки функция обратного вызова должна соответствующим образом выполнять соглашение "error-first callback". В Express, как показала практика, лучший метод состоит в распространении ошибок по цепочке промежуточных обработчиков с использованием функции next()
.
Более подробная информация об основных принципах обработки ошибок приведена в разделе:
- Обработка ошибок в Node.js
- Разработка устойчивых к сбоям приложений Node: обработка ошибок (корпоративный блог StrongLoop)
Чего не нужно делать¶
Вам точно не нужно обрабатывать событие uncaughtException
, порожденное при передаче исключительной ситуации обратно в цикл ожидания событий. Добавление обработчика события uncaughtException
изменит стандартное поведение процесса, в котором произошла исключительная ситуация; процесс продолжит выполнение, несмотря на исключительную ситуацию. На первый взгляд, это неплохой способ защиты от сбоя приложения. Однако выполнение приложения после того, как произошла необрабатываемая исключительная ситуация, весьма опасно само по себе и не может быть рекомендовано: состояние процесса становится ненадежным и непредсказуемым.
Кроме того, использование uncaughtException
официально считается crude, и имеется вариант proposal исключить его из ядра. Значит, обработка uncaughtException
- идея неудачная. Поэтому мы и рекомендуем иметь несколько процессов и супервизоров: часто самым надежным способом восстановления после ошибки является удаление и перезапуск системы.
Также мы не рекомендуем использовать модуль domains. Он очень редко помогает решить проблему и является устаревшим.
Метод try-catch¶
Конструкция try-catch в языке JavaScript позволяет перехватывать исключительные ситуации в синхронном коде. Например, при помощи try-catch можно обрабатывать ошибки анализа JSON, как показано ниже.
Инструменты типа JSHint или JSLint помогут вам найти неявные исключительные ситуации, подобные описанным в разделе Ошибки ReferenceError в неопределенных переменных.
Ниже приводится пример использования конструкции try-catch для обработки потенциальной исключительной ситуации, приводящей к отказу процесса. Этот промежуточный обработчик принимает параметр поля запроса "params", который является объектом JSON.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Однако конструкция try-catch работает только для синхронного кода. Поскольку платформа Node является преимущественно асинхронной (в частности, в рабочей среде), с помощью конструкции try-catch удастся перехватить не так уж много исключительных ситуаций.
Метод Promises¶
Промисы (promises) обрабатывают любые исключительные ситуации (явные и неявные) в блоках асинхронного кода, в которых используется метод then()
. Просто добавьте .catch(next)
в конце цепочки промисов. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Теперь все ошибки, асинхронные и синхронные, будут передаваться в промежуточный обработчик ошибок.
Однако здесь необходимо разъяснить два момента:
- Весь асинхронный код должен возвращать промисы (кроме отправителей). Если какая-то библиотека не возвращает промисы, преобразуйте объект base при помощи вспомогательной функции типа Bluebird.promisifyAll().
- Отправители событий (такие как потоки) могут вызывать необрабатываемые исключительные ситуации. Поэтому проверьте правильность обработки событий ошибки; например:
1 2 3 4 5 6 7 8 |
|
Дополнительная информация об обработке ошибок с использованием промисов приведена в разделе:
- Обработка ошибок асинхронного кода в Express с использованием промисов, генераторов и ES7
- Промисы в Node.js с Q - альтернатива функции обратного вызова
Что можно сделать в среде / при настройке¶
Ниже приведены некоторые примеры того, что можно сделать в среде функционирования системы для улучшения производительности приложений.
- Задать в переменной
NODE_ENV
значениеproduction
- Обеспечить автоматический перезапуск приложения
- Выполнять приложение в кластере
- Сохранять результаты запросов в кэше
- Использовать распределитель нагрузки
- Использовать обратный прокси-сервер
Задать в переменной NODE_ENV значение "production"¶
Переменная среды NODE_ENV
задает среду выполнения приложения (обычно это среда разработки или рабочая среда). Простейший способ улучшить производительность - задать в переменной NODE_ENV
рабочую среду (значение production
).
Если NODE_ENV
имеет значение production
, то в Express:
- сохраняются в кэше шаблоны представления;
- сохраняются в кэше файлы CSS, сгенерированные из расширений CSS;
- генерируются менее подробные сообщения об ошибках.
Тестирование показывает, что в результате только этих действий производительность увеличивается втрое.
Если вам необходимо написать код для определенной среды, значение переменной NODE_ENV
можно проверить в process.env.NODE_ENV
. Следует помнить, что при проверке значения любой переменной среды производительность снижается, поэтому желательно производить эту операцию пореже.
В среде разработки переменные среды обычно указываются в интерактивной оболочке, например при помощи export
или файла .bash_profile
. На рабочем сервере лучше использовать систему инициализации ОС (systemd или Upstart). В следующем разделе мы уделим больше внимания системе инициализации в целом, но задание значения переменной NODE_ENV настолько важно для производительности (и при этом настолько легко достижимо), что рассматривается здесь отдельно.
Для Upstart укажите в своем файле файле задания ключевое слово env
. Например:
1 2 |
|
Дополнительная информация приведена в разделе Upstart: введение, справочное руководство и лучшие практические методы.
Для systemd укажите директиву Environment
в своем файле юнитов. Например:
1 2 |
|
Дополнительная информация приведена в разделе Использование переменных среды в юнитах systemd.
При работе с StrongLoop Process Manager можно также задать переменную среды во время установки StrongLoop PM как службы.
Обеспечить автоматический перезапуск приложения¶
В рабочей среде приложение не должно отключатся ни при каких условиях. Следовательно, необходимо обеспечить его перезапуск не только при сбое самого приложения, но и при сбое сервера. Надеясь на то, что этого не случится, мы должны быть реалистами и на всякий случай подготовиться, чтобы:
- использовать диспетчер процессов для перезапуска приложения (и Node), когда произойдет его сбой;
- использовать систему инициализации ОС для перезапуска диспетчера процессов, когда произойдет сбой ОС. Систему инициализации можно использовать и без диспетчера процессов.
При наступлении необрабатываемой исключительной ситуации в приложениях Node происходит сбой. Поэтому самое главное, что нужно сделать, - обеспечить, чтобы приложение было тщательно протестировано и обрабатывало все исключительные ситуации (дополнительная информация приведена в разделе "Правильно обрабатывать исключительные ситуации"). А для устойчивости к отказам необходимо иметь механизм, который обеспечит автоматический перезапуск приложения, если произойдет его сбой.
Использовать диспетчер процессов¶
В среде разработки запустить приложение можно прямо из командной строки, указав node server.js
или нечто подобное. В рабочей среде это верный путь к беде: в случае сбоя приложение будет отключено до тех пор, пока вы не выполните его перезапуск. Для того чтобы приложение перезапускалось после сбоя, используется диспетчер процессов. Диспетчер процессов - это "контейнер" для приложений, обеспечивающий развертывание и высокую готовность и позволяющий управлять приложением в среде выполнения.
Помимо перезапуска приложения после сбоя, диспетчер процессов позволяет:
- получать аналитическую информацию о производительности среды выполнения и потреблении ресурсов;
- изменять параметры в динамическом режиме в целях повышения производительности;
- управлять кластеризацией (StrongLoop PM и pm2).
Наиболее популярные диспетчеры процессов перечислены ниже:
Сравнение трех диспетчеров процессов по каждой характеристике можно найти в разделе http://strong-pm.io/compare/. Более подробное представление трех диспетчеров приведено в разделе Диспетчеры процессов для приложений Express.
Наличие любого из этих диспетчеров процессов позволит обеспечить работоспособность приложения даже в случае возможных сбоев.
Однако StrongLoop PM имеет массу характеристик, рассчитанных специально на развертывание в среде выполнения. StrongLoop и связанные с ним инструменты позволяют:
- разрабатывать приложение и создавать его пакет в локальной системе и развертывать его в безопасном режиме в рабочей системе;
- автоматически перезапускать приложение после его сбоя независимо от причины;
- управлять кластерами в удаленном режиме;
- просматривать профайлы CPU и моментальные снимки кучи в целях оптимизации производительности и диагностирования утечек памяти;
- просматривать показатели производительности приложения;
- легко масштабировать для работы на нескольких хостах с возможностями встроенного управления распределителем нагрузки.
Как объясняется ниже, при установке StrongLoop PM в качестве службы операционной системы с помощью системы инициализации, этот диспетчер будет автоматически выполнять перезапуск после перезагрузки системы. То есть, поддерживать постоянную активность процессов и кластеров.
Использовать систему инициализации¶
Следующий уровень надежности призван обеспечить перезапуск приложения при перезапуске сервера. Системы могут зависать по разным причинам. Для перезапуска приложения в случае сбоя сервера используйте систему инициализации, встроенную в вашу ОС. На данный момент используются две основные системы инициализации - systemd и Upstart.
Системы инициализации можно использовать с приложением Express двумя способами:
- запустите приложение в диспетчере процессов и установите диспетчер процессов как службу в системе инициализации. Диспетчер процессов будет перезапускать приложение в случае сбоя приложения, система инициализации будет перезапускать диспетчер процессов в случае перезапуска ОС. Это рекомендуемый способ;
- запустите приложение (и Node) прямо в системе инициализации. Этот способ немного проще, но он лишает вас дополнительного преимущества - возможности использовать диспетчер процессов.
Systemd¶
Systemd - менеджер системы и служб для Linux. В большинстве основных дистрибутивов Linux systemd принят в качестве системы инициализации по умолчанию.
Файл конфигурации службы systemd имеет имя unit file с расширением .service. Ниже приведен пример файла юнитов для непосредственного управления приложением Node (вместо выделенного жирным шрифтом текста укажите значения для своей системы и приложения):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Дополнительная информация о systemd приведена в разделе Справочник по systemd (страница справки).
StrongLoop PM как служба systemd¶
Диспетчер процессов StrongLoop можно легко установить как службу systemd. В этом случае во время перезапуска сервера автоматически выполняется перезапуск StrongLoop PM; он, в свою очередь, перезапускает все приложения, которыми он управляет.
Для установки StrongLoop PM как службы systemd выполните следующие действия:
1 |
|
Затем запустите службу в следующем порядке:
1 |
|
Дополнительная информация приведена в разделе Настройка хоста рабочей среды (документация по StrongLoop).
Upstart¶
Upstart - системный инструмент, доступный во многих дистрибутивах Linux; позволяет запускать задачи и службы во время запуска системы, останавливать их во время выключения и осуществлять наблюдение за их работой. Если приложение Express или диспетчер процессов настроен как служба, Upstart будет автоматически перезапускать их в случае сбоя.
Служба Upstart определяется в файле конфигурации задания (другое название - "задание") с расширением .conf
. Ниже приведен пример создания задания "myapp" для приложения "myapp", где главный файл находится в каталоге /projects/myapp/index.js
.
Создайте файл myapp.conf
в каталоге /etc/init/
со следующим содержимым (вместо выделенного жирным шрифтом текста укажите значения для своей системы и приложения):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
ПРИМЕЧАНИЕ. Для этого сценария требуется Upstart 1.4 или старшей версии с поддержкой в Ubuntu 12.04-14.10.
Поскольку задание настроено для выполнения при запуске системы, ваше приложение будет запускаться вместе с операционной системой и автоматически перезапускаться в случае сбоя приложения или зависания системы.
Помимо автоматического перезапуска приложения, Upstart позволяет выполнять следующие команды:
start myapp
- запуск приложенияrestart myapp
- перезапуск приложенияstop myapp
- остановка приложения
Дополнительная информация об Upstart приведена в разделе Upstart: введение, справочное руководство и лучшие практические методы.
StrongLoop PM как служба Upstart¶
Диспетчер процессов StrongLoop можно легко установить как службу Upstart. В этом случае во время перезапуска сервера автоматически выполняется перезапуск StrongLoop PM; он, в свою очередь, перезапускает все приложения, которыми он управляет.
Для установки StrongLoop PM как службы Upstart 1.4 выполните следующие действия:
1 |
|
Затем запустите службу в следующем порядке:
1 |
|
ПРИМЕЧАНИЕ. В системах, не поддерживающих Upstart 1.4, команды будут иметь некоторые отличия. Дополнительная информация приведена в разделе Настройка хоста рабочей среды (документация по StrongLoop).
Выполнять приложение в кластере¶
В многоядерных системах производительность приложения Node можно увеличить многократно, если запустить группу процессов. В группе выполняется несколько экземпляров приложения, в идеале - один экземпляр на каждом ядре ЦП, что позволяет распределять нагрузку и задачи по экземплярам.
ВАЖНОЕ ЗАМЕЧАНИЕ. Экземпляры приложения выполняются как отдельные процессы, поэтому они используют разные пространства памяти. То есть, объекты будут локальными для каждого экземпляра приложения. Значит, в коде приложения состояние не сохраняется. Зато можно использовать хранилище данных в оперативной памяти типа Redis, в котором будут храниться связанные с сеансом данные и данные о состоянии. Эта оговорка относится по сути ко всем формам горизонтального масштабирования - в равной мере к и группам процессов, и к группам физических серверов.
В кластерных приложениях сбой может произойти в процессах отдельного экземпляра приложения, не оказывая влияния на другие процессы. Помимо преимуществ улучшения производительности, изоляция сбоев также говорит в пользу выполнения процессов приложений в кластере. При любом сбое процесса экземпляра приложения обязательно занесите событие в протокол и породите новый процесс, используя метод cluster.fork()
.
Использовать модуль cluster Node¶
Поддержка кластеров возможна благодаря модулю Node cluster module. Он позволяет главному процессу порождать процессы экземпляра приложения и распределять входящие соединения между экземплярами приложения. Но лучше использовать не сам этот модуль, а один из его инструментов, который будет выполнять необходимые действия автоматически, например node-pm или cluster-service.
Использовать StrongLoop PM¶
Если приложение развернуто в диспетчере процессов StrongLoop Process Manager (PM), вы можете пользоваться поддержкой кластеров, не изменяя код приложения.
Когда диспетчер процессов StrongLoop Process Manager (PM) выполняет приложение, то приложение автоматически будет выполняться в кластере с числом экземпляров приложения, равным числу ядер ЦП в системе. В кластере число процессов экземпляра приложения невозможно изменить вручную при помощи инструмента командной строки slc без остановки приложения.
Например, если вы развернули приложение на prod.foo.com и StrongLoop PM слушает соединения на порте 8701 (значение по умолчанию), укажите размер кластера, равный восьми, используя slc:
1 |
|
Дополнительная информация о поддержке кластеров при помощи StrongLoop PM приведена в разделе Кластеризация документации по StrongLoop.
Сохранять результаты запросов в кэше¶
Еще одна стратегия улучшения производительности в рабочей среде заключается в том, чтобы сохранять в кэше результат запросов, тогда приложению не нужно будет повторять операцию для многократного обслуживания этого запроса.
Используйте сервер кэширования типа Varnish или Nginx (см. также Кэширование Nginx), чтобы существенно увеличить быстродействие и производительность своего приложения.
Использовать распределитель нагрузки¶
Вне зависимости от оптимизации приложения отдельный экземпляр может обработать лишь определенную часть рабочей нагрузки и передаваемых данных. Один из способов масштабирования приложения состоит в запуске нескольких его экземпляров и распределении передаваемых данных при помощи распределителя нагрузки. Настройка распределителя нагрузки может улучшить производительность и быстродействие приложения и нарастить его возможности сверх того, что может делать один экземпляр приложения.
Распределителем нагрузки обычно выступает обратный прокси-сервер, который управляет передачей данных между несколькими экземплярами приложений и серверами. Распределитель нагрузки приложения можно легко настроить при помощи Nginx или HAProxy.
При работе с распределителем нагрузки рекомендуется убедиться, что запросы, связанные с определенным идентификатором сеанса, подключены к породившему их процессу. Это называется привязка к сеансу или закрепленные сеансы, и решается с помощью описанной выше рекомендации использовать для сеансовых данных хранилище данных типа Redis (в зависимости от приложения). Описание приведено в разделе Использовать несколько узлов.
Использовать StrongLoop PM с распределителем нагрузки Nginx¶
StrongLoop Process Manager интегрируется с Nginx Controller, позволяя легко настраивать конфигурации рабочих сред на нескольких хостах. Дополнительная информация приведена в разделе Масштабирование на нескольких серверах (документация по StrongLoop).
Использовать обратный прокси-сервер¶
Обратный прокси-сервер расположен перед веб-приложением. Помимо направления запросов к приложению, он выполняет операции поддержки запросов. В частности, он способен обрабатывать страницы ошибок, операции сжатия, кэширования, обслуживания файлов и распределения нагрузки.
Передача задач, для которых не требуется знать состояние приложения, обратному прокси-серверу разгружает Express для выполнения специализированных прикладных задач. В связи с этим в рабочей среде рекомендуется располагать Express за обратным прокси-сервером типа Nginx или HAProxy.