Как сервер Node.js (например, Express) управляет памятью, а не сервером PHP? - PullRequest
2 голосов
/ 20 мая 2019

Из того, что я понимаю, в основном, серверные PHP-приложения (PHP-FPM) загружают все приложение с нуля при каждом запросе, а затем закрывают его в конце запроса.Это означает, что переменные, контейнеры, конфиг и все остальное читаются и собираются с нуля в каждом отдельном запросе, и пересечения нет.Я могу использовать эти знания, чтобы лучше структурировать приложение.Например, я знаю, что статические классы хранят свои данные только на время запроса, и каждый новый запрос будет иметь свое собственное значение.

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

Поэтому у меня есть несколько вопросов по этому поводу:

  1. Имеет ли смысл предварительно загружать некоторые данные во время запуска Express.js (например, чтение секретных ключей из файла), чтобы они уже находились в памяти, когда это необходимо для запроса, и каждый раз они использовались бы повторновремя без перечитывания из файла?В среде PHP-сервера это не будет иметь большого значения, так как все собирается с нуля с каждым запросом.
  2. Как правильно обрабатывать исключения в процессе сервера Node.js.Если скрипт сервера PHP генерирует фатальное исключение только в том случае, если конкретный запрос умирает, все остальные запросы и любые новые выполняются нормально.Если на сервере Node.js происходит фатальная ошибка, похоже, что он уничтожит весь процесс и, следовательно, все запросы к нему.

Если у вас есть какие-либо ресурсы о том, как работает эта тема, этобыло бы замечательно, если бы вы могли поделиться ими также.

Ответы [ 2 ]

3 голосов
/ 20 мая 2019

1-

Имеет ли смысл предварительно загружать некоторые данные во время запуска Express.js (например, чтение закрытых ключей из файла), чтобы они уже находились в памяти, когда это требуется по запросу, и каждый раз использовались бы повторно без повторного использования? читать из файла? В среде PHP-сервера это не имеет большого значения, так как все строится из 0 с каждым запросом.

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

В этом сценарии необходимо учесть несколько моментов:

  • Во время запуска приложения вы можете безопасно вызывать синхронные методы, такие как fs.readFileSync и т. Д., Поскольку в данный момент нет единого запроса для одного потока.

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

secrets.js

const fs = require('fs');
const gmailSecretApiKey = fs.readFileSync('path_to_file');
const mailgunSecretApiKey = fs.readFileSync('path_to_file'); 
...

module.exports = {
gmailSecretApiKey,
mailgunSecretApiKey,
...
}

Тогда require это как запуск вашего приложения. После этого любые модули, которые делают: const gmailKey = require('.../secrets').gmailSecretApiKey не будет читать из файла снова. Результаты кешируются в модуле.

Это важно, потому что позволяет вам использовать require и import для использования конфигурации в ваших контроллерах и модулях, не беспокоясь о передаче дополнительных параметров вашим http-контроллерам или добавлении их в req объекты.

  • В зависимости от инфраструктуры вы не сможете разрешить приложению не обрабатывать запросы во время запуска (т. Е. У вас есть только одна машина, и вы не хотите давать service unavailble своим клиентам). В таких случаях вы можете выставлять все настройки и общие ресурсы в обещаниях и загружать свои веб-контроллеры как можно быстрее , ожидая получения обещаний внутри. Допустим, нам нужно, чтобы kafka был запущен и запущен при обработке запроса к / user:

kafka.js

function kafka() {
    // return some promise of an object that can publish and read from kafka in a given port etc. etc.
  }
module.exports = kafka();

Так что теперь в:

userController.js

const kafka = require('.../kafka');
router.get('/user', (req,res) => {
  kafka.then(k => {
    k.publish(req.user, 'userTopic'); // or whatever. This is just an example.
  });
})

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

  • Нет такой вещи, как несколько потоков в узле. Все, что вы объявляете в модуле commonJS или пишете в process, будет доступно в каждом запросе.

2-

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

Это действительно зависит от того, какое исключение вы найдете. Это конкретно связано с обрабатываемым запросом или что-то критичное для всего приложения?

В первом случае вы хотите перехватить исключение и не позволить всему потоку умереть. Теперь «поймать исключение» в javascript сложно , потому что вы не можете catch асинхронные исключения / ошибки, и вы, вероятно, будете использовать process.on('unhandledRejection') для обработки этого, например:

// main.js

try {
  bootstrapMongoDb();
  bootstrapKafka();
  bootstrapSecrets();
  ... wahtever
  bootstrapExpress();
} catch(e){
   // read what `e` brings and decide.
   // however, is worth to mention that errors raised during handling
   // http request won't ever get handled here, because they are
   // asynchronous. try/catch in javascript don't catch asynchronous errors.
 }

process.on('unhandledRejection', e => {
  // now here we are treating unhandled promise rejections, and errors that raise
  // in express controllers are likely end up here. of course, I'm talking about
  // promise rejections. I am not sure if this can catch Errors thrown in callbacks.
 // You should never `throw new Error` inside an asynchronous callback. 

});

Обработка ошибок в приложении узла - это отдельная тема, слишком широкая, чтобы рассматривать ее здесь. Однако некоторые советы не должны причинять вреда:

  • Никогда не выбрасывайте ошибки в обратных вызовах. throw синхронно. Обратные вызовы и асинхронность должны основываться на параметре error или отклонении обещания.

  • Тебе лучше привыкнуть к обещаниям. Обещания действительно улучшают управление ошибками в асинхронном коде.

  • Ошибки Javascript могут быть украшены дополнительными полями, так что вы можете заполнить идентификаторы трассировки и другие идентификаторы, которые могут быть полезны при чтении журналов вашей системы, если вы будете регистрировать ваши необработанные ошибки.

Теперь, в последнем случае ... иногда случаются сбои, которые совершенно губительны для вашего приложения.Возможно, вам совершенно необходимо подключение к серверу kafka или mongo, и если оно сломано, вы можете захотеть убить ваше приложение, чтобы клиенты получали 503 при попытке подключения.

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

Если у вас нет инфраструктуры, которая контролирует работоспособность и перезагрузку вашего веб-сервиса, вероятно, безопаснее никогдапусть ваше приложение умирает.Сказано, что хорошо по крайней мере использовать такие инструменты, как nodemon или PM2, чтобы гарантировать, что ваше приложение будет перезапущено после выхода из строя.

Бонус: почему вы не должны выбрасывать ошибки в обратных вызовах

Брошенные ошибкираспространяется через callstack.Допустим, у вас есть функция A, которая вызывает B, которая затем вызывает C. Затем C выдает ошибку.Все они имеют только синхронный код.

В этом случае ошибка распространяется на B и, если она не catch, она распространяется на A и т. Д.

Теперьскажем, что вместо этого C не выдает ошибку самостоятельно, а вызывает fs.readFile(path, callback).В функции обратного вызова выдается ошибка.

Здесь, когда вызывается обратный вызов и выдается ошибка, A уже завершена и покинула стек давно, сотни миллисекунд назад, может быть, даже больше.

Это означает, что любой catch блок в A не поймает ошибку, потому что даже там уже нет :

function bootstrapTimeout() {

try {
  setTimeout(() => {
    throw new Error('foo');
    console.log('paco');
    }, 200);

} catch (e) {
 console.log('error trapped!');
} 

}

function bootstrapInterval() {

  setInterval(() => {
  console.log('interval')
  }, 50);
}

console.log('start');
bootstrapTimeout();
bootstrapInterval();

Если вы запустите этот фрагмент,вы увидите, как ошибка достигает верхнего уровня и завершает процесс, даже если строка throw new Error('foo'); была помещена в блок try / catch.

ошибка, интерфейс результата

Вместо использованияОшибки для обработки исключений в асинхронном коде, у node.js стандартное поведение - выставлять интерфейс (error, result) для каждого обратного вызова, передаваемого асинхронному методу.Если, например, fs.readFile происходит неправильно из-за того, что имя файла не существует, он не выдает ошибку, он вызывает обратный вызов с соответствующей ошибкой в ​​качестве параметра error.

Like:

fs.readFile('notexists.png', (error, callback) => {

  if(error){
    // foo
  }
  else {
    http.post('http://something.com', result, (error, callback) => {
      if(error){
        // oops, something went wrong with an http request
      } else {
        // keep working
        // etc.
        // maybe more callbacks, always with the dreadful 'if (error)'...
      }
    })
  }
});

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

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

fsReadFilePromise('something.png')
.then(res => someHttpRequestPromise(res))
.then(httpResponse => someOtherAsyncMethod(httpResponse))
.then(_ => maybeSomeLoggingOrWhatever() )
.catch(e => {
  // here you can control any error thrown in the previous chain.
});

А также есть асинхронное / ожидание, которое позволяет вам смешивать асинхронный и синхронизирующий код иОтноситесь к отказам от обещаний в блоках улова:

await function main() {
  try {
    a(); // some sync code
    await b(); // some promise
  } catch(e) {
    console.log(e); // either an error throw in a() or a promise rejection reason in b();
  }
}

Однако имейте в виду, что await не магия, и вам действительно нужно хорошо понимать обещания и асинхронность, чтобы правильно их использовать.

В конце вы всегда получаете один поток управления ошибками для синхронных ошибок через try/catch, а другой - для асинхронных ошибок, через параметры обратного вызова или отклонения обещания.

Обратные вызовы может использовать try/catch при использовании синхронных API, но никогда не должен throw.Любая функция может использовать catch для обработки синхронных ошибок, но не может полагаться на блоки catch для обработки асинхронных ошибок.Вроде грязный.

1 голос
/ 20 мая 2019

Имеет ли смысл предварительно загружать некоторые данные во время запуска Express.js (например, чтение закрытых ключей из файла), чтобы они уже находились в памяти, когда это необходимо для запроса, и каждый раз использовались бы повторно без повторного использования? читать из файла?

Да, имеет смысл, если вы структурируете свой код, чтобы эти данные были доступны в обработчике запросов. В следующем примере, основываясь на том, что я знаю, staticResponse читается только один раз.

const express = require('express');
const staticResponse = fs.readFileSync('./data');
const app = express();

app.get('/', function (req, res) {
  res.json(staticResponse);
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

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

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

all requests with it => имейте в виду, что nodejs является однопоточным.

app.post('/', function (req, res, next) {
    try {
        const data = JSON.parse(req.body.stringedData);

        // use data

        res.sendStatus(200);

    } catch (err) {
        return next(err);
    }
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...