Node.js:内存使用量不断增加

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

我们发现这个脚本的内存使用量不断增加,并最终被系统(Ubuntu)杀死;

我们多次检查了这个看似简单的脚本。 因此,我发布了下面的代码。 任何意见/build议,非常感谢!

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

为了便于排除故障,下面是一个独立的(没有第三方库)testing用例。

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

我们相信问题出在setInterval()循环中 – 也许这些图像在发送之后没有从内存中删除(也许仍然保存在variables“res”中?)。

任何意见/build议,非常感谢!

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

有几件事引起了这一点

  • MJPEG并不是devise用来以高频率发送高分辨率的dynamic图像(25fps,对于320×240帧,对于720p可能没问题)。只要考虑有效负载25fps * 70KB = 1750KBps = 14Mbps的输出吞吐量高于全高清video。
  • 当客户端无法接收时,Node.js会将输出caching在缓冲区中。 因为你发送给客户端的数据很多,所以节点为你保存了它们。 这就是为什么你的内存使用永远不会下降,这不是内存泄漏。 要检测输出缓冲区是否膨胀,请检查res.write()的返回值。
  • setInterval()可以使用,只要客户端保持连接,不会造成任何问题。 但是,当客户端断开连接时,您需要停止它。 为此,您需要监视“closures”事件。
  • 你不能使用MJPEG来保持稳定的fps,因为它不是为此目的而devise的,所以不pipe你怎么努力,都不能控制客户端的fps。 但是仔细devise的代码,你可以使用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); 

当然,它泄漏内存。 你做

  setInterval(...) 

对每个请求,但你永远不会清理这些间隔。 这意味着(例如)20个请求之后,您有20个间隔在后台运行,即使客户端/连接长时间死亡,也将永远运行。 其中一个解决scheme如下:

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

这确保了在连接closures后my_interval (和所有相应的数据)将被清除。

PS我build议使用setTimeout而不是setInterval ,因为加载文件可能需要更多的时间然后frameDelay这将导致问题。

PS2。 使用fs函数的asynchronous版本。 Node.JS的整个function处于非阻塞操作状态,并且在这里失去了主要的优势(性能)。