
Лучшие практики Node.js: производительность и надежность
Лучшие практики для приложений Node.js
Это будет серия из двух статей, в которых мы увидим, как улучшить производительность нашего приложения. В этой статье мы обсудим, что мы можем сделать на уровне кода для повышения производительности. Вторая статья будет охватывать часть OP (окружающая среда / настройка).
Я рассказываю о вещах, связанных с приложениями Express, над которыми я работал. Мы также можем использовать эти методы во всех типах веб-приложений, хотя и с некоторыми изменениями.

Использовать сжатие gzip
Использование сжатия Gzip может значительно уменьшить размер тела ответа и, следовательно, увеличить скорость веб-приложения. Используйте промежуточное ПО сжатия для сжатия gzip в приложении Express, как показано ниже:
var compression = require('compression')
var express = require('express')
var app = express()
app.use(compression())
Скажите НЕТ синхронным функциям
Синхронные функции и методы связывают выполняющийся процесс до тех пор, пока не вернутся. Один вызов синхронной функции может вернуться через несколько микросекунд или миллисекунд, однако на веб-сайтах с высоким трафиком эти вызовы складываются и снижают производительность приложения. Избегайте его использования в производстве.
Хотя Node и многие модули предоставляют синхронные и асинхронные версии своих функций, всегда используйте асинхронную версию в производственной среде. Единственный раз, когда синхронная функция может быть оправдана, - это при первоначальном запуске.
Если мы используем Node.js 4.0+ или io.js 2.1.0+, мы можем использовать флаг командной строки --trace-sync-io для вывода предупреждения и трассировки стека всякий раз, когда наше приложение использует синхронный API. Конечно, мы не хотели бы использовать это в производственной среде, а скорее для того, чтобы наш код был готов к работе.
Правильная регистрация
В общем, есть две причины для ведения журнала из нашего приложения: для отладки и для ведения журнала активности приложения (по сути, всего остального). Использование console.log() или console.error() для вывода сообщений журнала на терминал - обычная практика в разработке. Но эти функции синхронны, когда местом назначения является терминал или файл, поэтому они не подходят для производства, если мы не передаем вывод в другую программу.
Для отладки
Если мы ведем журнал в целях отладки, то вместо использования console.log() используйте специальный модуль отладки, такой как debug. Этот модуль позволяет нам использовать переменную среды DEBUG для управления тем, какие отладочные сообщения отправляются на console.error(), если таковые имеются. Чтобы наше приложение оставалось чисто асинхронным, мы по-прежнему хотим передать console.error() другой программе. Но тогда мы не собираемся отлаживать в продакшене, не так ли?
Для активности в приложении
Если мы регистрируем активность приложения (например, отслеживание трафика или вызовы API), вместо использования console.log(), мы можем использовать библиотеку журналов, такую как Winston или Bunyan.
Использовать параметры проверки
Приложение, написанное на node.js без обработки исключений, может выйти из строя - в случае некоторых недопустимых входных параметров, например. недопустимое преобразование типа, слишком длинная строка и т. д. Это означает перерыв в работе нашего приложения.
Часто эти недопустимые параметры передаются через функции API в базу данных, что может вызывать исключения и сообщения об ошибках.
Любая функция в остальном api должна проверять правильность входных параметров в самом начале, прежде чем отправлять их в базу данных или в другие функции.
Есть много готовых пакетов, которые можно использовать для этой цели. Один из них - экспресс-валидатор и идеально подходит для производства. С помощью этого модуля мы можем проверять данные тела, запросов и заголовков.
Как его использовать. Ниже приведен только один из способов его использования. Мы можем изменить его в зависимости от наших потребностей и требований.
1. Поместите модуль первым в главный файл сервера node.js, например. index.js или server.js
var util = require('util'),
bodyParser = require('body-parser'),
express = require('express'),
expressValidator = require('express-validator'),
app = express();
app.use(bodyParser.json());
// this line must be immediately after any of the bodyParser middlewares!
app.use(expressValidator({
errorFormatter: function(param, msg, value) {
var namespace = param.split('.')
,root = namespace.shift()
,formParam = root;
while(namespace.length) {
formParam += '[' + namespace.shift() + ']';
}
return {
param : formParam,
msg : msg,
value : value
};
}
}));
app.listen(8888);
2. Поместите приведенный ниже фрагмент кода в новый файл, скажем, в validator.js. Он будет использоваться для проверки во многих файлах нашего приложения.
module.exports = function(request,response,schema)
{
request.checkBody(schema);
var errors = request.validationErrors();
if (errors) {
console.error({Error: errors});
response.status(422).send({Error: {Validator: errors}});
return true;
}
else return false;
}
3. Объявите схему для проверки входных параметров в отдельном файле.
module.exports = { 'sensor_device_id': {isInt:{ errorMessage: 'sensor_device_id - Integer expected' }, errorMessage: 'sensor_device_id is required'}, 'active': {notEmpty: true, errorMessage: 'Active Validation error'}, 'organisation': {isInt:{ errorMessage: 'Organisation - Integer expected' }, errorMessage: 'Organisation is required'} }
3. Разместите инструкции по каждому действию схемы. Нам нужно указать правильное имя файла с определенной схемой.
if(validator(request,response,schema)) return next();
Например -
const validator = require('../schema/validator.js'); const schema_sensorActive = require('../schema/sensorActive.js');app.post('/sensor', function (request, response, next) {if(validator(request,response,schema_sensorActive)) return next();const mssql = request.service.mssql; const params = request.body; const { organisation=null, sensor_device_id=null, active=1 } = params;const sqlQuery = 'EXEC [AppCenter].[Sensor_Active_UPDATE] ?, ?, ?, ?'; const sqlParams = [request.auth.UserID, organisation, sensor_device_id, active];mssql.query(sqlQuery, sqlParams, { success(res) {try{ return response.status(200).send(res);} catch(err) { return response.status(201).send({Error: res}); } }, error: handleSqlError(response, sqlQuery, sqlParams) }); }
Каждый раз, когда Валидатор обнаруживает ошибку, информация об этом будет отправлена клиентскому приложению внешнего интерфейса. Таким же образом мы также можем использовать проверку для выходных параметров, например, после выполнения данных из базы данных, с устройства и т. Д.
Правильная обработка исключений
Любое приложение выйдет из строя при обнаружении неперехваченного исключения. Если не обрабатывать исключения и не предпринимать соответствующих действий, наше приложение выйдет из строя и отключится. Существует способ, с помощью которого мы можем автоматически запускать приложение в случае сбоев (о котором я расскажу во второй части), и, к счастью, приложения Express обычно имеют короткое время запуска.
Тем не менее, мы в первую очередь хотим избежать сбоев, и для этого нам нужно правильно обрабатывать исключения.
Чтобы гарантировать обработку всех исключений, используйте следующие методы:
- Используйте try-catch
- Используйте обещания
Прежде чем углубляться в эти темы, мы должны иметь базовое представление об обработке ошибок Node / Express: использование обратных вызовов, выполняемых сначала при ошибках, и распространение ошибок в промежуточном программном обеспечении. Node использует соглашение «обратный вызов сначала ошибка» для возврата ошибок из асинхронных функций, где первым параметром функции обратного вызова является объект ошибки, за которым следуют данные результата в последующих параметрах. Чтобы указать отсутствие ошибки, передайте null в качестве первого параметра. Функция обратного вызова должна соответственно следовать соглашению об обратном вызове при первой ошибке, чтобы осмысленно обрабатывать ошибку. А в Express лучше всего использовать функцию next () для распространения ошибок по цепочке промежуточного программного обеспечения.
Чего не делать
Одна вещь, которую мы должны не делать, - это прислушиваться к событию uncaughtException, которое возникает, когда исключение возвращается обратно в цикл обработки событий. Добавление прослушивателя событий для uncaughtException изменит поведение по умолчанию для процесса, который обнаруживает исключение; процесс продолжит работу, несмотря на исключение. Это может показаться хорошим способом предотвратить сбой нашего приложения, но продолжать запускать приложение после неперехваченного исключения является опасной практикой и не рекомендуется, поскольку состояние процесса становится ненадежным и непредсказуемым.
Кроме того, использование uncaughtException официально признано сырым. Так что слушать uncaughtException - просто плохая идея. Также не рекомендую использовать домены. Обычно это не решает проблему и является устаревшим модулем.
Используйте try-catch
Try-catch - это языковая конструкция JavaScript, которую мы можем использовать для перехвата исключений в синхронном коде. Используйте try-catch, например, для обработки ошибок синтаксического анализа JSON, как показано ниже.
Используйте такие инструменты, как JSHint или JSLint, чтобы помочь нам найти неявные исключения, такие как ссылочные ошибки на неопределенные переменные.
Вот пример использования try-catch для обработки исключения, которое может привести к сбою процесса. Эта функция промежуточного программного обеспечения принимает параметр поля запроса с именем «params», который является объектом JSON.
app.get('/search', function (req, res) {
// Simulating async operation
setImmediate(function () {
var jsonStr = req.query.params
try {
var jsonObj = JSON.parse(jsonStr)
res.send('Success')
} catch (e) {
res.status(400).send('Invalid JSON string')
}
})
})
Однако try-catch работает только для синхронного кода. Поскольку платформа Node в основном асинхронна (особенно в производственной среде), try-catch не обнаружит много исключений.
Используйте обещания
Промисы будут обрабатывать любые исключения (как явные, так и неявные) в блоках асинхронного кода, которые используют then(). Просто добавьте .catch(next) в конец цепочки обещаний. Например:
app.get('/', function (req, res, next) { // do some sync stuff queryDb() .then(function (data) { // handle data return makeCsv(data) }) .then(function (csv) { // handle csv }) .catch(next) })app.use(function (err, req, res, next) { // handle error })
Теперь все асинхронные и синхронные ошибки передаются промежуточному программному обеспечению ошибок.
Однако есть два предостережения:
- Весь наш асинхронный код должен возвращать обещания (кроме эмиттеров). Если конкретная библиотека не возвращает обещания, преобразуйте базовый объект с помощью вспомогательной функции, например Bluebird.promisifyAll ().
- Источники событий (например, потоки) могут вызывать неперехваченные исключения. Поэтому убедитесь, что мы правильно обрабатываем событие ошибки; Например:
const wrap = fn => (...args) => fn(...args).catch(args[2])app.get('/', wrap(async (req, res, next) => { const company = await getCompanyById(req.query.id) const stream = getLogoStreamById(company.id) stream.on('error', next).pipe(res) }))
Функция wrap() - это оболочка, которая перехватывает отклоненные обещания и вызывает next() с ошибкой в качестве первого аргумента.
Оптимизация запросов к БД
И последняя очень важная проблема: даже супер-написанный код в node.js будет работать медленно, когда ответ от сервера БД займет больше нескольких секунд, что переводится во время ответа нашего Rest API.
Вторая важная вещь - это использовать больше обработки данных на стороне TSQL в БД, а не на стороне node.js.
Больше контента на plainenglish.io