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

问题描述 投票:134回答:10

有些日子我一直在搜索错误的工作解决方案

Error: EMFILE, too many open files

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

sysctl -w kern.maxfiles=20480

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

第二个问题:

经过多次搜索后,我找到了解决“太多打开文件”问题的方法:

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框架来开发这个应用程序。然后我发现了节点,我必须说非阻塞I / O模型的想法非常好,诱人,而且最重要的是非常快!

但是我应该对节点有什么样的问题?它是经过生产验证的Web服务器吗?你有什么经历?

javascript macos node.js file-descriptor
10个回答
73
投票

因为当graceful-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相关。

命令参考

使用此语法确定进程已打开的打开句柄数...

To get a count of open files for a certain pid

我使用此命令来测试在我的应用程序中执行各种事件后打开的文件数。

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

What is your process limit?

ulimit -a

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

Permanently change the limit:

  • 在Ubuntu 14.04上测试,nodejs v.7.9

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

  • file:/etc/pam.d/common-session(添加到最后) session required pam_limits.so
  • file:/etc/security/limits.conf(添加到最后,或编辑,如果已经存在) root soft nofile 40000 root hard nofile 100000
  • 从ssh重新启动nodejs并注销/登录。
  • 这可能不适用于旧版NodeJS,您需要重启服务器
  • 如果您的节点使用不同的uid运行,请使用。

0
投票

cwait是一种通用的解决方案,用于限制任何返回promise的函数的并发执行。

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

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

68
投票

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


6
投票

我今天遇到了这个问题,找不到好的解决方案,我创建了一个模块来解决它。我的灵感来自@ 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
        }
    });
});

4
投票

你正在读太多文件。节点异步读取文件,它将立即读取所有文件。所以你可能正在阅读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()

2
投票

我刚刚写完了一小段代码来解决这个问题,所有其他解决方案看起来都太重了,需要你改变你的程序结构。

这个解决方案只是停止任何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);
        });
    }
};

2
投票

我不确定这是否会对任何人有所帮助,我开始研究一个有很多依赖项的大项目,这给我带来了同样的错误。我的同事建议我使用brew安装watchman,并为我解决了这个问题。

brew update
brew install watchman

1
投票

随着风笛,你只需要改变

FS.readFile(filename, onRealRead);

=>

var bagpipe = new Bagpipe(10);

bagpipe.push(FS.readFile, filename, onRealRead))

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


1
投票

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


1
投票

像我们所有人一样,您是异步I / O的另一个受害者。使用异步调用,如果循环遍历大量文件,Node.js将开始为每个要读取的文件打开文件描述符,然后等待操作直到您关闭它。

文件描述符保持打开状态,直到服务器上的资源可用于读取它。即使您的文件很小并且读取或更新速度很快,也需要一些时间,但同时您的循环不会停止打开新文件描述符。因此,如果您有太多文件,很快就会达到限制,您将获得一个漂亮的EMFILE。

有一种解决方案,创建一个队列来避免这种影响。

感谢编写Async的人,有一个非常有用的功能。有一个名为Async.queue的方法,您创建一个带有限制的新队列,然后将文件名添加到队列中。

注意:如果必须打开许多文件,最好存储当前打开的文件,并且不要无限次重新打开它们。

const fs = require('fs')
const async = require("async")

var q = async.queue(function(task, callback) {
    console.log(task.filename);
    fs.readFile(task.filename,"utf-8",function (err, data_read) {
            callback(err,task.filename,data_read);
        }
    );
}, 4);

var files = [1,2,3,4,5,6,7,8,9,10]

for (var file in files) {
    q.push({filename:file+".txt"}, function (err,filename,res) {
        console.log(filename + " read");
    });
}

您可以看到每个文件都添加到队列(console.log文件名),但仅限于当前队列低于您之前设置的限制。

async.queue通过回调获取有关队列可用性的信息,只有在读取数据文件并完成您必须执行的任何操作时才会调用此回调。 (参见fileRead方法)

所以你不能被文件描述符所淹没。

> node ./queue.js
0.txt
    1.txt
2.txt
0.txt read
3.txt
3.txt read
4.txt
2.txt read
5.txt
4.txt read
6.txt
5.txt read
7.txt
    1.txt read (biggest file than other)
8.txt
6.txt read
9.txt
7.txt read
8.txt read
9.txt read
© www.soinside.com 2019 - 2024. All rights reserved.