Подтвердить что ты не робот

Node.js/Express throws 'RangeError: максимальный размер стека вызовов превышен' при высокой нагрузке

У нас проблема с нашей средой node, работающей под высоким который нам не удалось найти.

Немного фона: мы запускаем кластерное приложение node, используя Экспресс для инфраструктуры http. В настоящее время существует 3 коробки с 8 Процессорные ядра на каждом, и каждый ящик запускает кластер из 6 node работников. Настройка, похоже, отлично работает, и я исследовал все предлагаемые методологии, так что я считаю, что настройка прочная. Мы node.js 0.8.1 с Express 2.5.11 и XMLHttpRequest 1.4.2.

Здесь проблема: мы делаем тест "темного запуска" этого продукта (то есть код клиента браузера имеет javascript-вызовы ajax для наших API-интерфейсов в фон, но не используется на странице или отображается пользователю). После нескольких минут работы система бросает:

[RangeError: Maximum call stack size exceeded]

Мы поймаем ошибку с событием "uncaughtException" в кластерный контроллер (который запускает каждого рабочего), однако нет стека на этом уровне. Я провел обширные исследования по эта проблема и, похоже, не может найти никого с подобной ошибкой. После расчесывая КАЖДУЮ строку кода в системе, вот что я знаю:

  • Я не могу найти рекурсии или круглые ссылки. (Я читал, что эта ошибка не всегда означает проблему рекурсии, но мы проверили; мы фактически запускаем тесты, удаляя большую часть кода в любом случае, и это все еще происходит, см. ниже);
  • Я перешел к одному рабочему процессу в поле, чтобы попытаться устранить кластер как проблема - проблема все еще происходит;
  • Проблема ТОЛЬКО происходит при высокой нагрузке. Наш трафик составляет ок. 1500 страниц в секунду и, во время интенсивного движения, может достигать 15000 страниц в секунду (мы не смогли реплицировать на dev окружающая среда);
  • Время обнаружения ошибки меняется, но обычно составляет 15 минут;
  • Ошибка, похоже, не влияет на работу! Под этим я подразумеваю, что нет коррумпированных ответов и, кроме случайного таймаута, система никогда не сбой;
  • Рабочий процесс, который перехватывает ошибку, восстанавливается и начинает выполнять снова запрашивает через несколько секунд;
  • Я получил ошибку, которая произошла в самом базовом дизайне - нет вызываются дополнительные API. Просто сделайте запрос и ответьте простой ответ json. Это самая любопытная часть. Это не кажется например, система не работает ни в одном из моих кодов - она ​​не работает без создавая экземпляр любого из классов для выполнения реальной работы. Очевидно, что я начался с большего количества кода, но медленно доставал кусочки, пока он не был по-прежнему не удается выполнить настройку "без костей".

Самый яркий симптом, я считаю, заключается в том, что ошибка всегда происходит ПОСЛЕ того, как запрос был полностью подан. То есть сервер принимает запрос, находит правильный экспресс-маршрут, вызывает res.send и законченный. Это действительно похоже на сборку мусора для меня! я прочел что двигатель V8 имеет очень хороший двигатель GC, но мне интересно, как большая часть нашей тяжелой нагрузки влияет на вещи.

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

Кластерный контроллер. Это очищенная версия того, что запущено в командной строке.

cluster = require('cluster');
path = require('path');
fs = require('fs');
app = require('./nodeApi');
_ = require('underscore');
nodeUtil = require(./nodeUtil);

process.on('uncaughtException', function(err) {
  var stamp;
  stamp = new Date();
  console.log("***************************** Exception Caught, " + stamp);
  return console.log("Exception is:", err);
});

if (cluster.isMaster) {
  if ((nodeUtil.isLiveServer() || nodeUtil.isCluster()) && process.env.IS_CLUSTER !== '0') {
    numCPUs = require("os").cpus().length - 2;
    if (numCPUs <= 0) {
      numCPUs = 1;
    }
  } else {
    numCPUs = 1;
  }
  console.log("Forking " + numCPUs + " workers...");
  for (i = _i = 1; 1 <= numCPUs ? _i <= numCPUs : _i >= numCPUs; i = 1 <= numCPUs ? ++_i : --_i) {
    worker = cluster.fork();
  }
} else {
  app.start();
}

nodeWorker.. Использование Express и простой маршрут для обслуживания запрос. Запрос завершается обратным вызовом, если используется jsonp (для нашего тестирование с помощью ajax, это было необходимо)

(function() {
  var crypto, express, fs, modroot, path, staticroot, _;
  express = require('express');
  _ = require('underscore');
  fs = require('fs');
  path = require('path');

  module.exports.start = function() {
    logFile = fs.createWriteStream("" + logpath + "/access.log", {
      flags: 'a'
    });

    app = express.createServer();

    app.configure(function() {
      app.use(express.logger({
        stream: logFile,
        format: ':remote-addr - [:date] - ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" :response-time ms'
      }));
      app.use(express.errorHandler({
        dumpExceptions: true,
        showStack: true
      }));
      app.use(express.cookieParser());
      app.use(express.bodyParser());
      app.use(express.session({
        secret: "ourMemStoreSecret",
        cookie: {
          domain: ".ourdomain.com"
        },
        maxAge: new Date(Date.now() + 7200000),
        // The store WAS a redis store.  I took it out to eliminate redis as the issue.  We don't use sessions anyway.
        store: new require('express').session.MemoryStore({
          reapInterval: 60000 * 15
        })
      }));
      app.use(express["static"](staticroot));
      app.set('view engine', 'underscore');  // For our template rendering.  Not used in this test.
      app.set('views', __dirname + '/views/src');
      app.set('view options', {
        layout: false
      });
      app.use(app.router);
    });

    ignore = function(req, res, next) {
      if (req.params.api === 'favicon.ico') {
        return next('route');
      }
      return next();
    };

    wrapCallback = function(req, res, next) {
      var callbackName;
      if (callbackName = req.query.callback) {
        req.wrapCallback = true;
        res._send = res.send;
        res.send = function(data, status) {
          var dataString;
          if (_.isObject(data)) {
            dataString = encodeURI(JSON.stringify(data));
            res.setHeader('Content-Type', 'application/javascript');
            return res._send("" + callbackName + "(\"" + dataString + "\")", status);
          } else {
            data = encodeURI(data);
            return res._send("" + callbackName + "(\"" + data + "\")", status);
          }
        };
      }
      return next();
    };

    app.error(function(err, req, res, next) {
      console.log("[" + process.pid + "] Error Handler. Ok.", err);
      return res.send({
        error: err.msg
      }, err.statusCode);
    });

    // Does anyone know how to hard-code a path AND put it into a variable at the same time?
    // Kind of like: "/:api=MyTestAPI"  ??  That why this route is here.
    setAPIName = function(req, res, next) {
      req.params.api = 'MyTestAPI';
      return next();
    };
    app.get("/MyTestAPI", setAPIName, wrapCallback, function(req, res) {
      res.send({
        hello: 'world'
      }, 200);
      return console.log("[" + process.pid + "] res.send (no cacher) is done");
    });

    process.setMaxListeners(0);
    process.send({
      // For IPC - the controller has a handler for this message
      cmd: 'isStarted'
    });
    return app.listen(process.env.APP_PORT);
  };

}).call(this);

Как выглядит ошибка. В принципе, я никогда не вижу, чтобы это происходило в в середине запроса. На ошибке нет стека вызовов либо - это только сообщение. Здесь вы можете увидеть 2 каждый обрабатывает ответ, тогда ошибка на одном из их.

[660] res.send (no cacher) is done
[654] res.send (no cacher) is done
***************************** Exception Caught, Fri Nov 02 2012 10:23:48 GMT-0400 (EDT)

Я бы очень признателен за некоторые отзывы об этом. Система работает красиво и способно обрабатывать наш огромный трафик тремя ящиками. Нагрузка на ящики составляет около 40% и напевает. Мне бы очень хотелось найти источник этой проблемы, чтобы другие могли гордиться этой системой, как я, и покажите node.js неверующим, что это отличный продукт!

4b9b3361

Ответ 1

Я думал, что обновляю свой собственный пост, чтобы объяснить, какое исправление было для меня.

Поняв, что я сделал все остальное, я знал, как это сделать, решение представилось, сделав это:

Установить версию Express 3

Было так много различий и изменений, которые необходимо было внести в основной код, который потребовал целый день для преобразования. Тем не менее, я смог воспользоваться многими новыми v3 featuers, включая метод .param для подключения помощников к вашим переменным param в каждом маршруте. Это устранило несколько моих старых "вспомогательных" функций, поэтому вместо того, чтобы связывать маршруты, я использовал это вместо.

Теперь у меня есть полное понимание маршрутов/промежуточного программного обеспечения, и просто переписав для Express v3, моя проблема исчезла!

Поскольку это не точный ответ, это то, что я использовал, чтобы узнать, как сделать преобразование:

Экспресс-версия API v3

Информация о том, как работают маршруты

Awesome HOWTO doc! Спасибо этим парням!

Ответ 2

Я столкнулся с одной и той же проблемой в одной моей рабочей среде. Во время анализа я нашел следующие вещи, возможно, я ошибаюсь. Но я надеюсь, это поможет вам...

Эта проблема в основном связана с Socket. Есть вариант, сколько открытых соединений Socket должно принять? и может ли соединение оставаться наполовину открытым?

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

Позвольте мне объяснить...

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

  • В общем случае NodeJs может отлично работать, когда вы даете 2 запроса в 0-й секунде и остаетесь 2-м в 6-й секунде.

  • Вместо этого, если вы дадите 4 запроса в 0-й секунде, то NodeJs готовы обслуживать только 2 запроса. NodeJs - это просто закрытый сокет для оставшихся двух запросов. Примечание. Позже, если вы дадите тот же запрос, NodeJs примет и даст ответ.

  • Для получения дополнительной информации перейдите через реализацию socket.io.js.

И мое решение,

  • Создайте балансировщик нагрузки на сервере.
  • Запустите экземпляры или кластеры NodeJs под балансировщиком нагрузки.

Или Если вы найдете какой-либо другой простой способ решить эту проблему, обновите этот пост...

Я жду, чтобы узнать отличное решение этой проблемы.

Спасибо