节点和错误:EMFILE,打开的文件太多

有些日子,我已经寻找一个工作的解决scheme,以一个错误

Error: EMFILE, too many open files

看来很多人都有同样的问题。 通常的答案是增加文件描述符的数量。 所以,我试过这个:

sysctl -w kern.maxfiles=20480

默认值是10240.在我看来,这有点奇怪,因为我在目录下处理的文件数量在10240以下。甚至更奇怪的是,在增加了文件描述符的数量之后,我仍然收到相同的错误。

第二个问题:

经过多次search,我find了一个解决“太多打开文件”的问题:

 var requestBatches = {}; function batchingReadFile(filename, callback) { // First check to see if there is already a batch if (requestBatches.hasOwnProperty(filename)) { requestBatches[filename].push(callback); return; } // Otherwise start a new one and make a real request var batch = requestBatches[filename] = [callback]; FS.readFile(filename, onRealRead); // Flush out the batch on complete function onRealRead() { delete requestBatches[filename]; for (var i = 0, l = batch.length; i < l; i++) { batch[i].apply(null, arguments); } } } function printFile(file){ console.log(file); } dir = "/Users/xaver/Downloads/xaver/xxx/xxx/" var files = fs.readdirSync(dir); for (i in files){ filename = dir + files[i]; console.log(filename); batchingReadFile(filename, printFile); 

不幸的是我仍然收到相同的错误。 这段代码有什么问题?

最后一个问题(我是JavaScript和节点新手),我正在开发一个Web应用程序,其中有大约5000个日常用户的请求。 我有多年使用python和java等语言编程的经验。 所以本来我想用django或者play framework来开发这个应用程序。 然后我发现节点,我必须说,非阻塞I / O模型的想法是非常好,诱人,最重要的是非常快!

但是,我应该期待什么样的问题与节点? 它是一个生产validation的networking服务器? 你有什么经验?

使用Isaac Schlueter的graceful-fs模块(node.js维护者)可能是最合适的解决scheme。 如果遇到EMFILE,它会执行增量回退。 它可以用作内置fs模块的直接替代品。

因为当优美的fs不工作…或者你只是想知道泄漏来自何处。 按照这个过程。

(例如,如果你的问题与套接字有关,那么优雅-fs不会修理你的旅行车。)

从我的博客文章: http : //www.blakerobertson.com/devlog/2014/1/11/how-to-determine-whats-causing-error-connect-emfile-nodejs.html

如何隔离

这个命令将输出nodejs进程打开的句柄的数量:

lsof -i -n -P | grep nodejs

 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME ... nodejs 12211 root 1012u IPv4 151317015 0t0 TCP 10.101.42.209:40371->54.236.3.170:80 (ESTABLISHED) nodejs 12211 root 1013u IPv4 151279902 0t0 TCP 10.101.42.209:43656->54.236.3.172:80 (ESTABLISHED) nodejs 12211 root 1014u IPv4 151317016 0t0 TCP 10.101.42.209:34450->54.236.3.168:80 (ESTABLISHED) nodejs 12211 root 1015u IPv4 151289728 0t0 TCP 10.101.42.209:52691->54.236.3.173:80 (ESTABLISHED) nodejs 12211 root 1016u IPv4 151305607 0t0 TCP 10.101.42.209:47707->54.236.3.172:80 (ESTABLISHED) nodejs 12211 root 1017u IPv4 151289730 0t0 TCP 10.101.42.209:45423->54.236.3.171:80 (ESTABLISHED) nodejs 12211 root 1018u IPv4 151289731 0t0 TCP 10.101.42.209:36090->54.236.3.170:80 (ESTABLISHED) nodejs 12211 root 1019u IPv4 151314874 0t0 TCP 10.101.42.209:49176->54.236.3.172:80 (ESTABLISHED) nodejs 12211 root 1020u IPv4 151289768 0t0 TCP 10.101.42.209:45427->54.236.3.171:80 (ESTABLISHED) nodejs 12211 root 1021u IPv4 151289769 0t0 TCP 10.101.42.209:36094->54.236.3.170:80 (ESTABLISHED) nodejs 12211 root 1022u IPv4 151279903 0t0 TCP 10.101.42.209:43836->54.236.3.171:80 (ESTABLISHED) nodejs 12211 root 1023u IPv4 151281403 0t0 TCP 10.101.42.209:43930->54.236.3.172:80 (ESTABLISHED) .... 

注意:1023u(最后一行) – 这是默认最大值的第1024个文件句柄。

现在,看最后一栏。 这表明哪个资源是打开的。 您可能会看到多个行都具有相同的资源名称。 希望这现在告诉你在哪里查看代码泄漏。

如果您不知道多个节点进程,请首先查找哪个进程具有pid 12211.这会告诉您该进程。

在上面的例子中,我注意到有一堆非常相似的IP地址。 他们都是54.236.3.###通过做ip地址查找,能够确定在我的情况下,这是pubnub相关。

命令参考

使用这个语法来确定一个进程打开了多less个打开的句柄。

获得某个pid的打开文件的计数

我使用这个命令来testing在我的应用程序中执行各种事件之后打开的文件的数量。

lsof -i -n -P | grep "8465" | wc -l

 # lsof -i -n -P | grep "nodejs.*8465" | wc -l 28 # lsof -i -n -P | grep "nodejs.*8465" | wc -l 31 # lsof -i -n -P | grep "nodejs.*8465" | wc -l 34 

你的stream程限制是什么?

ulimit -a

你想要的行看起来像这样: open files (-n) 1024

永久改变极限:

  • 在Ubuntu 14.04上testing,nodejs v。7.9

如果你期望打开许多连接(websocket是一个很好的例子),你可以永久增加限制:

  • 文件:/etc/pam.d/common-session (加到最后)

     session required pam_limits.so 
  • 文件:/etc/security/limits.conf (添加到最后,如果已经存在则编辑)

     root soft nofile 40000 root hard nofile 100000 
  • 重启你的nodejs并从ssh注销/login。

  • 这可能不适用于较旧的NodeJS,您将需要重新启动服务器
  • 而不是如果你的节点运行不同的uid。

我今天遇到这个问题,没有find好的解决scheme,我创build了一个模块来解决它。 我受@ fbartho的片段启发,但是想避免覆盖fs模块。

我写的模块是Filequeue ,你可以像使用fs一样使用它:

 var Filequeue = require('filequeue'); var fq = new Filequeue(200); // max number of files to open at once fq.readdir('/Users/xaver/Downloads/xaver/xxx/xxx/', function(err, files) { if(err) { throw err; } files.forEach(function(file) { fq.readFile('/Users/xaver/Downloads/xaver/xxx/xxx/' + file, function(err, data) { // do something here } }); }); 

你一次读的文件太多了。 节点读取文件asynchronous,所以你会一次读取所有文件。 所以你可能一次读10240。

看看这是否工作:

 var fs = require('fs') var events = require('events') var util = require('util') var path = require('path') var FsPool = module.exports = function(dir) { events.EventEmitter.call(this) this.dir = dir; this.files = []; this.active = []; this.threads = 1; this.on('run', this.runQuta.bind(this)) }; // So will act like an event emitter util.inherits(FsPool, events.EventEmitter); FsPool.prototype.runQuta = function() { if(this.files.length === 0 && this.active.length === 0) { return this.emit('done'); } if(this.active.length < this.threads) { var name = this.files.shift() this.active.push(name) var fileName = path.join(this.dir, name); var self = this; fs.stat(fileName, function(err, stats) { if(err) throw err; if(stats.isFile()) { fs.readFile(fileName, function(err, data) { if(err) throw err; self.active.splice(self.active.indexOf(name), 1) self.emit('file', name, data); self.emit('run'); }); } else { self.active.splice(self.active.indexOf(name), 1) self.emit('dir', name); self.emit('run'); } }); } return this }; FsPool.prototype.init = function() { var dir = this.dir; var self = this; fs.readdir(dir, function(err, files) { if(err) throw err; self.files = files self.emit('run'); }) return this }; var fsPool = new FsPool(__dirname) fsPool.on('file', function(fileName, fileData) { console.log('file name: ' + fileName) console.log('file data: ', fileData.toString('utf8')) }) fsPool.on('dir', function(dirName) { console.log('dir name: ' + dirName) }) fsPool.on('done', function() { console.log('done') }); fsPool.init() 

我刚刚写完一小段代码来自己解决这个问题,所有其他的解决scheme似乎太重量级,并要求你改变你的程序结构。

这种解决scheme只是拖延任何fs.readFile或fs.writeFile调用,以便在任何给定的时间,在飞行中不超过一个设定的数字。

 // Queuing reads and writes, so your nodejs script doesn't overwhelm system limits catastrophically global.maxFilesInFlight = 100; // Set this value to some number safeish for your system var origRead = fs.readFile; var origWrite = fs.writeFile; var activeCount = 0; var pending = []; var wrapCallback = function(cb){ return function(){ activeCount--; cb.apply(this,Array.prototype.slice.call(arguments)); if (activeCount < global.maxFilesInFlight && pending.length){ console.log("Processing Pending read/write"); pending.shift()(); } }; }; fs.readFile = function(){ var args = Array.prototype.slice.call(arguments); if (activeCount < global.maxFilesInFlight){ if (args[1] instanceof Function){ args[1] = wrapCallback(args[1]); } else if (args[2] instanceof Function) { args[2] = wrapCallback(args[2]); } activeCount++; origRead.apply(fs,args); } else { console.log("Delaying read:",args[0]); pending.push(function(){ fs.readFile.apply(fs,args); }); } }; fs.writeFile = function(){ var args = Array.prototype.slice.call(arguments); if (activeCount < global.maxFilesInFlight){ if (args[1] instanceof Function){ args[1] = wrapCallback(args[1]); } else if (args[2] instanceof Function) { args[2] = wrapCallback(args[2]); } activeCount++; origWrite.apply(fs,args); } else { console.log("Delaying write:",args[0]); pending.push(function(){ fs.writeFile.apply(fs,args); }); } }; 

用风笛,你只需要改变

 FS.readFile(filename, onRealRead); 

=>

 var bagpipe = new Bagpipe(10); bagpipe.push(FS.readFile, filename, onRealRead)) 

风笛帮助你限制平行。 更多细节: https : //github.com/JacksonTian/bagpipe

运行nodemon命令时遇到同样的问题,所以我减less了在崇高文本中打开的文件的名称,并且错误消失了。

cwait是限制任何返回promise的函数的并发执行的通用解决scheme。

在你的情况下,代码可能是这样的:

 var Promise = require('bluebird'); var cwait = require('cwait'); // Allow max. 10 concurrent file reads. var queue = new cwait.TaskQueue(Promise, 10); var read = queue.wrap(Promise.promisify(batchingReadFile)); Promise.map(files, function(filename) { console.log(filename); return(read(filename)); })