递归 - 节点内存不足

时间:2014-11-23 22:41:13

标签: javascript node.js recursion express v8

当我运行以下代码9999999次时,Node返回:

FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory
Aborted (core dumped)

除了增加max alloc size或任何命令行参数之外,什么是解决此问题的最佳解决方案?

我希望提高代码质量,而不是破解解决方案。

以下是应用程序中主要的递归。

该应用程序是一种负载测试工具。

a.prototype.createClients = function(){
    for(var i = 0; i < 999999999; i++){
        this.recursiveRequest();
    }
}

a.prototype.recursiveRequest = function(){
    var self    =   this;
    self.hrtime =   process.hrtime();

    if(!this.halt){
        self.reqMade++;

        this.http.get(this.options, function(resp){
            resp.on('data', function(){})
                .on("connection", function(){
                })
                .on("end", function(){
                    self.onSuccess();
                });
        })
            .on("error", function(e){
                self.onError();
            });
    }
}

a.prototype.onSuccess = function(){
    var elapsed     =   process.hrtime(this.hrtime),
        ms          =   elapsed[0] * 1000000 + elapsed[1] / 1000

        this.times.push(ms);
        this.successful++;
        this.recursiveRequest();
}

4 个答案:

答案 0 :(得分:1)

看起来你应该使用队列而不是递归调用。 async.queue为处理异步队列提供了一种很棒的机制。您还应该考虑使用request模块使您的http客户端连接更简单。

var async = require('async');
var request = require('request');

var load_test_url = 'http://www.testdomain.com/';
var parallel_requests = 1000;

function requestOne(task, callback) {
  request.get(task.url, function(err, connection, body) {
    if(err) return callback(err);
    q.push({url:load_test_url});
    callback();
  });
}

var q = async.queue(requestOne, parallel_requests);

for(var i = 0; i < parallel_requests; i++){
  q.push({url:load_test_url});
}

您可以根据要与测试服务器匹配的同时请求数来设置parallel_requests变量。

答案 1 :(得分:1)

您正在推出10亿&#34;客户&#34;并行地,让它们中的每一个在无限递归中递归地执行http get请求。

几点评论:

  • 当你的问题提及1000万个客户端时,你的代码创建了10亿个客户端。
  • 您应该用递归函数替换for循环,以消除内存不足错误。

这些内容:

a.prototype.createClients = function(i){
    if (i < 999999999) {
        this.recursiveRequest();
        this.createClients(i+1);
    }
}
  • 然后,您可能希望在客户端创建之间或在recursiveRequest的调用之间包含一些延迟。使用setTimeout
  • 您应该有办法让递归停止(onSuccessrecursiveRequest继续相互呼叫)
  • async node.js模块这样的流控制库可以提供帮助。

答案 2 :(得分:0)

1000万非常大...假设堆栈支持任意数量的调用,它工作,但你可能要求JavaScript解释器加载1000万x相当多的内存......结果是内存不足。

另外,我个人不明白为什么你想要同时拥有这么多请求(测试服务器上的重负载?)一种优化方法是不创建&#34;浮动函数&# 34;你做了很多。 &#34;浮动功能&#34;在每个实例化中使用自己的内存集。

this.http.get(this.options, function(resp){ ... });
                            ^^^^
                            ++++--- allocates memory x 10 million

这里function(resp)...声明在每次调用时分配更多内存。你想要做的是:

# either global scope:
function r(resp) {...}
this.http.get(this.options, r ...);

# or as a static member:
a.r = function(resp) {...};
this.http.get(this.options, a.r ...);

至少你会节省所有功能记忆。当然,这适用于您在 r 函数中声明的所有函数。特别是如果它们非常大。

如果你想使用这个指针(make r一个原型函数),你可以这样做:

a.prototype.r = function(resp) {...};

// note that we have to have a small function to use 'that'... probably not a good idea
var that = this;
this.http.get(this.options, function(){that.r();});

要避免that引用,您可以使用保存在全局中的实例。尽管如此,这仍然无法使用对象:

a.instance = new a;

// r() is static, but can access the object as follow:
a.r = function(resp) { a.instance.<func>(); }

使用该实例,您可以从静态r函数访问对象的函数。这可能是可以充分利用this引用的实际实现:

a.r = function(resp) { a.instance.r_impl(); }

答案 3 :(得分:0)

根据Daniel的评论,您的问题是您滥用for()来计算您要发送的请求总数。这意味着您可以对代码应用非常简单的修复,如下所示:

a.prototype.createClients = function(){
    this.recursiveRequest();
};

a.prototype.recursiveRequest = function(){
    var self    =   this;
    self.hrtime =   process.hrtime();

    if(!this.halt && this.successful < 10000000){
...

您的递归足以进行任何次数的测试。

但你做的事情永远不会放弃。你有一个halt变量,但看起来你没有将它设置为true。但是,要测试1000万次,您需要检查已发送的请求数。

我的&#34;修复&#34;假设onError()失败(没有递归)。您还可以更改代码以使用暂停标志,如下所示:

a.prototype.onSuccess = function(){
    var elapsed     =   process.hrtime(this.hrtime),
        ms          =   elapsed[0] * 1000000 + elapsed[1] / 1000

        this.times.push(ms);
        this.successful++;
        if(this.successful >= 10000000)
        {
            this.halt = true;
        }
        this.recursiveRequest();
}

请注意,您将在时间缓冲区推送ms一千万次。这是一张大桌子!您可能希望获得总数并在最后计算平均值:

   this.time += ms;

   // at the end:
   this.average_time = this.time / this.successful;