【问题标题】:Node.js: Memory usage keeps going upNode.js:内存使用量不断上升
【发布时间】:2012-11-01 03:42:50
【问题描述】:

我们正在编写一个脚本,它读取我们服务器上的大量 JPG 文件(无限,因为我们有另一个进程不断将 JPG 文件写入同一目录)并将它们作为固定的 MJPEG 流发送到用户的浏览器时间间隔(下面代码中的变量“frameDelay”)。这类似于 IP 摄像机的功能。

我们发现这个脚本的内存使用量一直在上升并且总是被系统杀死(Ubuntu);

我们已经多次检查过这个看似简单的脚本。因此,我在下面发布代码。非常感谢任何 cmets / 建议!

app.get('/stream', function (req, res) {
    res.writeHead(200, {
        'Content-Type':'multipart/x-mixed-replace;boundary="' + boundary + '"',
        'Transfer-Encoding':'none',
        'Connection':'keep-alive',
        'Expires':'Fri, 01 Jan 1990 00:00:00 GMT',
        'Cache-Control':'no-cache, no-store, max-age=0, must-revalidate',
        'Pragma':'no-cache'
    });
res.write(CRLF + "--" + boundary + CRLF);

setInterval(function () {
    if(fileList.length<=1){
        fileList = fs.readdirSync(location).sort();
    }else{
        var fname = fileList.shift();
        if(fs.existsSync(location+fname)){
           var data = fs.readFileSync(location+fname);
            res.write('Content-Type:image/jpeg' + CRLF + 'Content-Length: ' + data.length + CRLF + CRLF);
            res.write(data);
            res.write(CRLF + '--' + boundary + CRLF);                   
            fs.unlinkSync(location+fname);
        }else{
            console.log("File doesn't find")
        }
    }
        console.log("new response:" + fname);
    }, frameDelay);
});

app.listen(port);
console.log("Server running at port " + port);

为方便故障排除过程,以下是一个独立的(无 3rd-party lib)测试用例。

它有完全相同的内存问题(内存使用量不断上升,最终被操作系统杀死)。

我们认为问题出在 setInterval () 循环中 - 可能这些图像在发送后没有从内存中删除或其他什么(可能仍存储在变量“res”中?)。

非常感谢任何反馈/建议!

var http                = require('http');
var fs                  = require('fs');

var framedelay  = 40;
var port                = 3200;
var boundary    = 'myboundary';
var CR                  = '\r';
var LF                  = '\n';
var CRLF                = CR + LF;

function writeHttpHeader(res)
{
        res.writeHead(200,
        {
                'Content-Type': 'multipart/x-mixed-replace;boundary="' + boundary + '"',
                'Transfer-Encoding': 'none',
                'Connection': 'keep-alive',
                'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
               'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
                'Pragma': 'no-cache',
        });

        res.write(CRLF + '--' + boundary + CRLF);
}

function writeJpegFrame(res, filename)
{
        fs.readFile('./videos-8081/frames/' + filename, function(err, data)
        {
                if (err)
                {
                        console.log(err);
                }
                else
                {
                        res.write('Content-Type:image/jpeg' + CRLF);
                        res.write('Content-Length:' + data.length + CRLF + CRLF);
                            res.write(data);
                        res.write(CRLF + '--' + boundary + CRLF);
                        console.log('Sent ' + filename);
                }
        });
}
http.createServer(function(req, res)
{
     writeHttpHeader(res)    
     fs.readdir('./videos-8081/frames', function(err, files)
     { 
         var i = -1;
         var sorted_files = files.sort();    
         setInterval(function()
         {
              if (++i >= sorted_files.length)
              {
                i = 0;
              }    
              writeJpegFrame(res, sorted_files[i]);
          }, framedelay);
        });
}).listen(port);
console.log('Server running at port ' + port);                    

【问题讨论】:

  • 您是否在客户端获得输出?你用的是什么模块?
  • 您的意思是将fileList 作为一个全局变量(由所有/stream 请求共享)?此外,您应该使用异步调用,否则这将无法很好地扩展。
  • 您看到的是恒定的、稳定的 70 fps 还是可变的?
  • 感谢大家的快速反馈 - 非常感谢。
  • 主要问题是内存使用情况。我编辑了我的原始帖子以包含一个不使用任何 3rd 方库的更简单的示例。

标签: node.js memory-management setinterval mjpeg


【解决方案1】:

有几件事造成了这种情况

  • MJPEG 并非旨在以高频率发送高分辨率运动图片(在您的情况下,25fps 可能适用于 320x240 帧,但不适用于 720p。)只需考虑有效负载的输出吞吐量 25fps*70KB = 1750KBps = 14Mbps,高于全高清视频。
  • 当客户端无法接收时,Node.js 会将输出缓存在缓冲区中。因为您要向客户端发送大量数据,所以节点为您保存了它们。这就是为什么您的内存使用量永远不会下降的原因,而且它 NOT 内存泄漏。要检测输出缓冲区是否已变大,请检查 res.write() 的返回值。
  • setInterval() 可以使用,只要客户端保持连接就不会出现任何问题。但是当客户端断开连接时,您需要停止它。为此,您需要监控“关闭”事件。
  • 您无法通过使用 MJPEG 来保持稳定的 fps,因为它不是为此目的而设计的,因此无论您如何努力,您都无法在客户端控制 fps。但是通过精心设计的代码,您可以使用setTimeout() 使平均fps稳定。

这里是固定代码。

var http = require('http');
var fs = require('fs');
var framedelay = 40;
var port = 3200;
var boundary = 'myboundary';
var CR = '\r';
var LF = '\n';
var CRLF = CR + LF;

http.createServer(function(req, res) {

  var files = fs.readdirSync('./imgs');
  var i = -1;
  var timer;
  var sorted_files = files.sort();

  res.writeHead(200, {
    'Content-Type': 'multipart/x-mixed-replace;boundary="' + boundary + '"',
    'Transfer-Encoding': 'none',
    'Connection': 'keep-alive',
    'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
    'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
    'Pragma': 'no-cache',
  });

  res.write(CRLF + '--' + boundary + CRLF);

  var writePic = function() {

    if (++i >= sorted_files.length)
      i = 0;

    var data = fs.readFileSync('./imgs/' + sorted_files[i]);
    res.write('Content-Type:image/jpeg' + CRLF);
    res.write('Content-Length:' + data.length + CRLF + CRLF);
    res.write(data);
    var ok = res.write(CRLF + '--' + boundary + CRLF);
    console.log('Sent ' + sorted_files[i], ok);

    if (ok)
      timer = setTimeout(writePic, framedelay);
  };

  res.on('close', function() {
    console.log('client closed');
    clearTimeout(timer);
  });

  res.on('drain', function() {
    console.log('drain');
    timer = setTimeout(writePic, framedelay);
  });

}).listen(port);

console.log('Server running at port ' + port);

【讨论】:

    【解决方案2】:

    当然它会泄漏内存。你会的

     setInterval(...)
    

    在每个请求上,但您从不清除这些间隔。这意味着在(例如)20 个请求之后,您将在后台运行 20 个间隔,即使客户端/连接早已失效,它们也会永远运行。解决方案之一如下:

    var my_interval = setInterval(function() {
        try {
            // all your code goes here
        } catch(e) {
            // res.write should throw an exception once the connection is dead
            // do the cleaning now
            clearInterval( my_interval );
        }
    }, frameDelay);
    
    req.on( "close", function() {
        // just in case
        clearInterval( my_interval );
    });
    

    这确保了my_interval(以及所有相应的数据)在连接关闭后将被清除。

    附:我建议使用setTimeout 而不是setInterval,因为加载文件可能需要比frameDelay 更多的时间,这会导致问题。

    附注 2。使用 fs 函数的异步版本。 Node.JS 的全部力量在于非阻塞操作,而您在这里失去了主要优势(性能)。

    【讨论】:

    • 另外 fs.unlinkSync 应该是一个 cron 任务。
    猜你喜欢
    • 2019-11-13
    • 2017-12-15
    • 1970-01-01
    • 2019-10-08
    • 2014-02-25
    • 2014-04-12
    • 1970-01-01
    • 2017-09-17
    • 2011-06-14
    相关资源
    最近更新 更多