node.js / Express抛出'RangeError:在高负载下超过'最大调用堆栈大小

时间:2012-11-02 19:16:50

标签: javascript node.js garbage-collection express stack-overflow

我们的节点环境在高位运行时遇到问题 加载我们无法找到的来源。

一点背景:我们使用的是运行集群节点应用程序 表示http框架。目前,有3个盒子,8个 每个CPU核心,每个盒子运行一个6节点的集群 工作人员。设置似乎很好,我已经研究了所有 建议的方法,我相信设置是可靠的。我们'再 使用Express 2.5.11和XMLHttpRequest 1.4.2运行node.js 0.8.1。

问题在于:我们正在进行一次黑暗的发布"测试该产品 (即浏览器客户端代码在我们的API中有javascript ajax调用 背景,但不在页面上使用或显示给用户)。成功运行几分钟后,系统正在抛出:

[RangeError: Maximum call stack size exceeded]

我们正在使用' uncaughtException'事件发生在 集群控制器(启动每个工作者),但没有 在该级别可用的堆栈跟踪。我做过广泛的研究 这个问题似乎找不到任何有类似错误的人。后 梳理系统中的每一行代码,这就是我所知道的:

  • 我找不到任何递归或循环引用。 (我已经读过了 这个错误并不总是意味着递归问题,但我们已经检查过; 我们实际上通过删除大部分代码来运行测试 仍然会发生,见下文);
  • 每个盒子我已经进行了1个工作流程以尝试消除 群集作为一个问题 - 问题仍然存在;
  • 问题只发生在高负荷下。我们的交通量约为。 每秒1500页,在交通繁忙时,可达到15000 每秒页数(我们还没有能够在dev上复制 环境);
  • 发现错误的时间有所不同,但通常在15分钟内;
  • 错误似乎不会影响操作!通过这个,我的意思是 没有腐败的反应,除了偶尔的超时, 系统永远不会崩溃;
  • 捕获错误的工作进程恢复并开始提供服务 几秒后再次请求;
  • 我在最基本的设计上遇到了错误 - 没有 调用其他API。只需提出请求并回复 简单的json响应。这是最奇怪的部分。它似乎并没有 就像系统在我的任何代码中都失败一样 - 它没有失败 实例化任何类来执行真正的工作。显然,我 从更多的代码开始,但慢慢地拿出碎片,直到它 仍然在一个简单的设置下失败。

我认为最明显的症状是错误总会发生 完全提供请求后。也就是说,服务器需要一个 请求,找到正确的Express路由,调用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's 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)

我真的很感激对此的一些反馈。系统运行 精美,能够用3箱处理我们的巨大交通。 盒子上的负载大约是40%并且嗡嗡作响。我很想找到 这个问题的来源,所以其他人可以像我一样为这个系统感到骄傲, 并向node.js非信徒展示这是一个很棒的产品!

2 个答案:

答案 0 :(得分:2)

我在我的一个生产环境中遇到过同样的问题。在分析过程中,我发现了以下事情,可能是我错了。但我希望,这会对你有帮助......

这个问题基本上与Socket有关。有一个选项可以接受多少个开放的Socket连接?连接可以保持半开吗?

通常情况下,这种异常只会因为您在特定时间段内点击服务器的频率而发生。

让我解释清楚......

  1. 假设只有两个套接字路径,你有四个请求,每个请求需要5秒的处理时间。

  2. 一般情况下,当您在第0秒提供2个请求并且在第6秒内提供2个请求时,NodeJ可以完美地提供服务。

  3. 而不是像这样,如果你在第0秒给出4个请求,那么NodeJ只准备服务2个请求。 NodeJs只是关闭套接字以保留两个请求。 注意:稍后如果您提供相同的请求,NodeJ将接受并给出响应。

  4. 有关详细信息,请参阅socket.io.js实现。

  5. 我的解决方案是,

    1. 以服务器友好的方式创建负载均衡器。
    2. 在负载均衡器下运行NodeJs实例或群集。
    3. 或者如果您找到任何其他简单方法来解决此问题,请更新此帖子...

      我等着知道这个问题的一个很好的解决方案。

      由于

答案 1 :(得分:2)

我以为我会更新自己的帖子来解释修复对我来说是什么。

在意识到我已经完成了其他所有我知道该怎么做的事情后,解决方案就是通过这样做来实现的:

安装Express版本3

核心代码需要做出很多差异和变化,我花了整整一天才进行转换。但是,在这样做的过程中,我能够利用许多新的v3特性,包括.param方法,用于将助手附加到每个路径中的:param变量。这消除了我的几个旧的“帮助”功能,所以我没有使用链接来代替链接。

我现在对路由/中间件有了充分的了解,只需重新编写Express v3,我的问题就消失了!

由于这不是一个确切的答案,这些是我用来学习如何进行转换的内容:

Express v3 API reference

Information on how routes work

Awesome HOWTO doc! Thanks to those guys!