首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >node.js / Express在高负载下抛出“RangeError:超过最大调用堆栈大小”

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

Stack Overflow用户
提问于 2012-11-03 03:16:50
回答 2查看 7.7K关注 0票数 20

我们的节点环境在高负载下运行时出现了一个问题,我们无法找到其根源。

一点背景知识:我们正在使用Express for the http框架运行一个集群节点应用程序。目前,有3个机器,每个机器上有8个CPU核心,每个机器运行一个由6个节点工作者组成的集群。设置似乎工作得很好,我研究了所有建议的方法,因此我相信设置是可靠的。我们运行的是带有Express2.5.11和XMLHttpRequest 1.4.2的node.js 0.8.1。

问题是:我们正在对该产品进行“暗启动”测试(即浏览器客户端代码在后台调用我们的API的javascript ajax,但不会在页面上使用,也不会显示给用户)。成功运行几分钟后,系统抛出:

代码语言:javascript
复制
[RangeError: Maximum call stack size exceeded]

我们使用集群控制器中的'uncaughtException‘事件捕获错误(启动每个工作进程),但是在该级别没有可用的堆栈跟踪。我在这个问题上做了大量的研究,似乎找不到有类似错误的人。在梳理了系统中的每一行代码后,我所知道的如下:

  • 我找不到任何递归或循环引用。(我读到这个错误并不总是意味着递归问题,但我们已经检查过了;我们实际上已经通过删除大多数代码来运行测试,但它仍然会发生,请参见下面的内容);
  • 我已经降低到每个框1个工作进程,试图将集群作为一个问题来消除--问题仍然会发生;
  • 问题只在高负载下发生。我们的交通大约是。1,500页每秒,在繁忙的交通时间,可以达到15000页每秒(我们还没有能够复制的开发environment);
  • The时间被捕获的时间各不相同,但通常是在15分钟内;
  • 错误似乎没有影响操作!这里,我的意思是没有损坏的响应,除了偶尔的超时之外,系统永远不会崩溃;
  • 捕获错误的工作进程恢复并在几秒钟后再次开始为请求提供服务;
  • 我在最基本的设计上遇到了错误--没有调用额外的API。只需接受请求并使用简单的json响应进行响应。这是最令人好奇的部分。在我的任何代码中,系统似乎都没有失败--它在没有实例化任何类来执行实际工作的情况下失败了。显然,我从更多的代码开始,但慢慢地删除了一些代码,直到它在最基本的设置下仍然失败。

我认为,最能说明问题的症状是,错误总是发生在请求被完全处理之后。也就是说,服务器接受一个请求,找到正确的快速路由,调用res.send,然后完成。对我来说,这真的像是垃圾回收!我读到过V8引擎有一个非常好的GC引擎,但我想知道我们沉重的负载会对事情产生多大的影响。

正如我所说的,即使在基本的设计上,代码也会抛出错误。删除了大部分自定义代码后,这是设置的基础。很抱歉我在这里插话了,所以并不是所有的变量声明等都会被包括在内,但是代码确实可以工作,所有的东西都在真正的代码中:

群集控制器。这是在命令行上启动的内容的清理版本。

代码语言:javascript
复制
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代码。使用和一个简单的路由来处理请求。如果使用jsonp,则请求被包装在回调中(对于我们的ajax测试,这是必需的)

代码语言:javascript
复制
(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个工作进程,每个工作进程提供一个响应,然后其中一个进程上的错误。

代码语言:javascript
复制
[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的人展示这是一个伟大的产品!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-01-03 03:16:25

我想我应该更新我自己的帖子来解释我的修复方法是什么。

在意识到我已经做了我知道如何做的所有其他事情之后,解决方案通过这样做呈现出来:

安装速成版本3

需要对核心代码进行如此多的差异和更改,以至于我花了整整一天的时间才完成转换。但是,在这样做的过程中,我能够利用许多新的v3特性,包括用于将助手附加到每个路径中的:param变量的.param方法。这消除了我的几个旧的"helper“函数,所以我没有通过路由链接,而是使用了它。

我现在对路由/中间件有了充分的了解,通过重写Express v3,我的问题就解决了!

由于这不是一个确切的答案,以下是我用来学习如何进行转换的东西:

Express v3 API reference

Information on how routes work

Awesome HOWTO doc! Thanks to those guys!

票数 2
EN

Stack Overflow用户

发布于 2012-12-28 15:33:20

我在我的一个生产环境中也遇到过同样的问题。在分析过程中,我发现了以下几点,可能是我错了。但我希望这能帮到你。

这个问题基本上与套接字有关。有一个选项是应该接受多少个打开的套接字连接?连接能保持半开吗?

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

让我解释清楚..。

假设只有两个socket路径,您有4个请求,每个请求需要5秒的处理时间。一般情况下,当您在0秒内发出2个请求,其余2个在第6秒内发出请求时,

  • 可以完美地服务。

  • 不是这样的,如果您在0秒内发出4个请求,那么NodeJs准备好只服务2个请求。NodeJs只是为剩下的两个请求关闭套接字。注意:如果您稍后发出相同的请求,NodeJs会接受并给出响应。

  • 有关更多信息,请访问socket.io.js NodeJs

我的解决方案是,

  1. 以服务器友好的方式创建负载均衡器。
  2. 在负载均衡器下运行NodeJs实例或集群。

或者如果你找到任何其他简单的方法来解决这个问题,请更新这篇文章…

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

谢谢

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13201667

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档