递归 - 节点内存不足

Recursion - Node out of memory

本文关键字:内存不足 节点 递归      更新时间:2023-09-26

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

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

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

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

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

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

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();
}

看起来你真的应该使用队列而不是递归调用。 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 变量。

您正在并行启动 10 亿个"客户端",并让每个客户端以无休止的递归方式递归地执行 http get 请求。

几点评论:

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

这些行中的内容:

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

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

此外,我个人不明白为什么您希望同时拥有如此多的请求(在服务器上测试负载过重?)一种优化方法是不要创建您正在做很多的"浮动函数"。"浮动函数"在每个实例化上使用自己的一组内存。

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 函数中声明的所有函数。特别是如果有相当大的。

如果你想使用 this 指针(使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(); }

根据 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 万次,您需要检查已发送的请求数。

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

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 1000 万次。那是一张大桌子!您可能希望有一个总计,并在最后计算一个平均值:

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