Node.js процесс из памяти в контуре http.request

На моем сервере node.js я не могу понять, почему в нем заканчивается память. Мой сервер node.js делает удаленный HTTP-запрос для каждого получаемого http-запроса, поэтому я попытался реплицировать проблему с помощью приведенного ниже примера script, который также исчерпал память.

Это происходит только в том случае, если итерации в цикле for очень высоки.

С моей точки зрения, проблема связана с тем, что node.js ставит в очередь удаленные HTTP-запросы. Как этого избежать?

Это пример script:

(function() {
  var http, i, mypost, post_data;
  http = require('http');
  post_data = 'signature=XXX%7CPSFA%7Cxxxxx_value%7CMyclass%7CMysubclass%7CMxxxxx&schedule=schedule_name_6569&company=XXXX';
  mypost = function(post_data, cb) {
    var post_options, req;
    post_options = {
      host: 'myhost.com',
      port: 8000,
      path: '/set_xxxx',
      method: 'POST',
      headers: {
        'Content-Length': post_data.length
      }
    };
    req = http.request(post_options, function(res) {
      var res_data;
      res.setEncoding('utf-8');
      res_data = '';
      res.on('data', function(chunk) {
        return res_data += chunk;
      });
      return res.on('end', function() {
        return cb();
      });
    });
    req.on('error', function(e) {
      return console.debug('TM problem with request: ' + e.message);
    });
    req.write(post_data);
    return req.end;
  };
  for (i = 1; i <= 1000000; i++) {
    mypost(post_data, function() {});
  }
}).call(this);


$ node -v
v0.4.9
$ node sample.js
FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory

Tks заранее

gulden PT

Ответ 1

Ограничение потока запросов на сервер

Можно предотвратить перегрузку встроенного Server и его вариантов HTTP/HTTPS, установив свойство maxConnections в экземпляр. Установка этого свойства приведет к остановке node accept() соединений и заставит операционную систему отбрасывать запросы при заполнении журнала listen() и приложение уже обрабатывает запросы maxConnections.

Отмена исходящих запросов

Иногда необходимо отключить исходящие запросы, как в примере script из вопроса.

Используя node напрямую или используя общий пул

Как показывает этот вопрос, неконтролируемое использование сетевой подсистемы node напрямую может привести к ошибкам памяти. Что-то вроде node-pool делает управление активным пулом привлекательным, но оно не решает фундаментальной проблемы безусловной очереди. Причиной этого является то, что node-pool не дает отзывов о состоянии пула клиентов.

UPDATE: Начиная с версии 1.0.7 node -pool включает патч, вдохновленный этим сообщением, чтобы добавить возвращаемое значение boolean к acquire(). Код в следующем разделе больше не нужен, и пример с шаблоном потоков - это рабочий код с node -pool.

Растрескивание открывает абстракцию

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

var useExplicitThrottling = function () {
  var active = 0
  var remaining = 10
  var queueRequests = function () {
    while(active < 2 && --remaining >= 0) {
      active++;
      pool.acquire(function (err, client) {
        if (err) {
          console.log("Error acquiring from pool")
          if (--active < 2) queueRequests()
          return
        }
        console.log("Handling request with client " + client)
        setTimeout(function () {
          pool.release(client)
          if(--active < 2) {
            queueRequests()
          }
        }, 1000)
      })
    }
  }
  queueRequests(10)
  console.log("Finished!")
}

Заимствование шаблона потоков

streams шаблон - это решение, которое идиоматично в node. Потоки имеют операцию write, которая возвращает false, когда поток не может буферизовать больше данных. Тот же шаблон может быть применен к объекту пула с acquire() возвратом false, когда было получено максимальное количество клиентов. Событие drain испускается, когда количество активных клиентов падает ниже максимума. Абстракция пула снова закрыта и позволяет опустить явные ссылки на размер пула.

var useStreams = function () {
  var queueRequests = function (remaining) {
    var full = false
    pool.once('drain', function() {
        if (remaining) queueRequests(remaining)
    })

    while(!full && --remaining >= 0) {
      console.log("Sending request...")
      full = !pool.acquire(function (err, client) {
        if (err) {
          console.log("Error acquiring from pool")
          return
        }
        console.log("Handling request with client " + client)
        setTimeout(pool.release, 1000, client)
      })
    }
  }
  queueRequests(10)
  console.log("Finished!")
}

Волокна

Альтернативное решение можно получить, предоставив блокирующую абстракцию в верхней части очереди. Модуль fibers предоставляет сопрограммы, которые реализованы в С++. Используя волокна, можно заблокировать контекст выполнения, не блокируя цикл событий node. Хотя я считаю, что этот подход является довольно элегантным, его часто упускают из виду в сообществе node из-за любопытного отвращения ко всем вещам синхронно. Обратите внимание, что, исключая утилиту callcc, фактическая логика цикла прекрасно лаконична.

/* This is the call-with-current-continuation found in Scheme and other
 * Lisps. It captures the current call context and passes a callback to
 * resume it as an argument to the function. Here, I've modified it to fit
 * JavaScript and node.js paradigms by making it a method on Function
 * objects and using function (err, result) style callbacks.
 */
Function.prototype.callcc = function(context  /* args... */) {
  var that = this,
      caller = Fiber.current,
      fiber = Fiber(function () {
        that.apply(context, Array.prototype.slice.call(arguments, 1).concat(
          function (err, result) {
            if (err)
              caller.throwInto(err)
            else
              caller.run(result)
          }
        ))
      })
  process.nextTick(fiber.run.bind(fiber))
  return Fiber.yield()
}

var useFibers = function () {
  var remaining = 10
  while(--remaining >= 0) {
    console.log("Sending request...")
    try {
      client = pool.acquire.callcc(this)
      console.log("Handling request with client " + client);
      setTimeout(pool.release, 1000, client)
    } catch (x) {
      console.log("Error acquiring from pool")
    }
  }
  console.log("Finished!")
}

Заключение

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

Ответ 2

да, вы пытаетесь поставить в очередь 1000000 запросов до их начала. Эта версия сохраняет ограниченное количество запросов (100):

  function do_1000000_req( cb )
  {
      num_active = 0;
      num_finished = 0;
      num_sheduled = 0;

      function shedule()
      {
         while (num_active < 100 && num_sheduled < 1000000) {
            num_active++;
            num_sheduled++;
            mypost(function() {
               num_active--;
               num_finished++;
               if (num_finished == 1000000)
               {
                  cb();
                  return;
               } else if (num_sheduled < 1000000)
                  shedule();
           });
         }
      }
  }

  do_1000000_req( function() {
      console.log('done!');
  });