JavaScript泄漏内存(Node.js / Restify / MongoDB)

更新4 :通过在函数之外实例化restify客户端(请参阅controllers / messages.js),并在每次请求之后调用global.gc(),似乎内存增长率已经降低了很多(每10secs约500KB)。 但是,内存使用量仍在不断增长。

Update3 :来到这篇文章: https : //journal.paul.querna.org/articles/2011/04/05/openssl-memory-use/

值得注意的是,我正在使用Restify的HTTPS。

更新2 :将下面的代码更新为当前状态。 我已经尝试用Express交换Restify。 可惜这没有任何区别。 似乎api调用链(restify – > mongodb – > external api)会导致所有内容都被保留。

更新1 :我用标准的MongoDB驱动程序replace了Mongoose。 内存使用似乎增长速度不够快,但泄漏仍然存在。


我一直在试图找出这个漏洞几天。

我使用Restify和Mongoose运行一个API,并且对于每个API调用,我都至less执行一次MongoDB查找。 我有大约1万2千个用户在一天内多次访问API。

我曾经尝试过

  • 我已经将我的代码隔离到只使用Restify并使用ApacheBench发起大量请求(100k +)。 testing期间,内存使用量保持在60MB左右。
  • 我已经隔离我的代码只使用Restify和Mongoose,并以上述相同的方式进行testing。 内存使用量保持在80MB左右。
  • 我已经使用ApacheBench在本地testing了完整的生产代码。 内存使用量保持在80MB左右。
  • 我已经自动倾倒堆的间隔。 我有最大的堆转储是400MB。 我只能看到有很多的string和数组,但我不能清楚地看到它的一个模式。

那么,有什么可能是错的?

我只使用一个API用户完成了上述testing。 这意味着mongoose只会一遍又一遍地抓住同一个文件。 与生产的区别在于很多不同的用户打了API意思,mongoose得到了很多不同的文档。

当我启动nodejs服务器时,内存很快增长到100MB-200MB。 最终稳定在500MB左右。 这是否意味着它泄漏每个用户的内存? 一旦每个用户访问API,它会稳定?

我在下面列出了我的代码,概述了我的API的一般结构。 我很想知道在我的代码中是否存在严重错误,或者是找出导致高内存使用率的其他方法。

app.js

var restify = require('restify'); var MongoClient = require('mongodb').MongoClient; // ... setup restify server and mongodb require('./api/message')(server, db); 

API / message.js

 module.exports = function(server, db) { // Controllers used for retrieving accounts via MongoDB and communication with an external api var accountController = require('../controllers/accounts')(db); var messageController = require('../controllers/messages')(); // Restify bind to put server.put('/api/message', function(req, res, next) { // Token from body var token = req.body.token; // Get account by token accountController.getAccount(token, function(error, account) { // Send a message using external API messageController.sendMessage(token, account.email, function() { res.send(201, {}); return next(); }); }); }); }; 

控制器/ accounts.js

 module.exports = function(db) { // Gets account by a token function getAccount(token, callback) { var ObjectID = require('mongodb').ObjectID; var collection = db.collection('accounts'); collection.findOne({ token: token }, function(error, account) { if (error) { return callback(error); } if (account) { return callback('', account); } return callback('Account not found'); }); } }; 

控制器/ messages.js

 module.exports = function() { function sendMessage(token, email, callback) { // Get a token used for external API getAccessToken(function() {} // ... Setup client // Do POST client.post('/external_api', values, function(err, req, res, obj) { return callback(); }); }); } return { sendMessage: sendMessage }; }; 

堆怀疑泄漏的快照 在这里输入图像描述

可能是getter中的一个bug,我使用virtual或getter来获取mongoose模式https://github.com/LearnBoost/mongoose/issues/1565

只看到string和数组实际上是正常的,因为大多数程序主要是基于它们的。 允许按对象总数进行sorting的分析器因此没有多大用处,因为它们多次为许多不同的程序提供相同的结果。

使用chrome的内存分析的一个更好的方法是例如在一个用户调用一个API之后获取一个快照,然后在另一个用户调用API之后获得第二个堆快照。

分析器提供了比较两个快照的可能性,看看这两个快照之间有什么区别(参见本教程 ),这将有助于理解内存为何以意想不到的速度增长。

对象被保留在内存中,因为仍然有一个引用来防止对象被垃圾收集。

因此,尝试使用分析器查找内存泄漏的另一种方法是查找您认为不应该在其中的对象,并查看它保留的path是什么,并查看是否有任何意外的path。

不知道这是否有帮助,但你可以尝试删除不必要的回报?

API / message.js

  // Send a message using external API messageController.sendMessage(token, account.email, function() { res.send(201, {}); next(); // remove 'return' }); 

控制器/ accounts.js

 module.exports = function(db) { // Gets account by a token function getAccount(token, callback) { var ObjectID = require('mongodb').ObjectID; var collection = db.collection('accounts'); collection.findOne({ token: token }, function(error, account) { if (error) { callback(error); // remove 'return' } else if (account) { callback('', account); // remove 'return' } else { callback('Account not found'); // remove 'return' } }); } return { // I guess you missed to copy this onto the question. getAccount: getAccount }; }; 

控制器/ messages.js

  // Do POST client.post('/external_api', values, function(err, req, res, obj) { callback(); // remove 'return' }); 

你的问题在getAccount和GC工作方式混在一起。

当你链接很多的function时,GC只能清除一个,而老的东西在内存上,所以你的获取帐户的机会越less,你至less需要6次调用global.gc()或自动执行之前,可以收集到这个时候GC采取的东西,它可能不会收集,所以它不检查它。

 collection{ findOne{ function(error, account){ callback('', account) sendMessage(...) getAccessToken(){ Post } } } } } } 

如Gene所示,删除此链接。

PS:这只是GC工作原理的一个表示,取决于实现,但你明白了。