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

时间:2012-01-22 23:18:35

标签: javascript macos node.js file-descriptor

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

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和节点的新手),我正在开发一个网络 应用程序有大量5000个日常用户的请求。我有多年的经验 使用python和java等其他语言编程。所以最初我想用django或play框架来开发这个应用程序。然后我发现了节点,我必须说非阻塞I / O模型的想法非常好,诱人,而且最重要的是非常快!

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

20 个答案:

答案 0 :(得分:73)

graceful-fs不起作用时......或者您只想了解泄漏的来源。按照这个过程。

(例如,如果您的问题是插座,那么graceful-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相关。

命令参考

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

获取某个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

您的流程限制是什么?

ulimit -a

您想要的行如下所示: open files (-n) 1024

永久更改限制:

  • 在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运行。

答案 1 :(得分:68)

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

答案 2 :(得分: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
        }
    });
});

答案 3 :(得分:5)

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

答案 4 :(得分:5)

我针对相同问题做了上述所有工作,但没有任何效果。我尝试在它下面工作100%。简单的配置更改。

选项1设置的限制(大部分时间都无效)

user@ubuntu:~$ ulimit -n 65535

检查可用限制

user@ubuntu:~$ ulimit -n
1024

选项2将可用限制增加到65535

user@ubuntu:~$ sudo nano /etc/sysctl.conf

在其中添加以下行

fs.file-max = 65535

运行此命令以刷新新配置

user@ubuntu:~$ sudo sysctl -p

编辑以下文件

user@ubuntu:~$ sudo vim /etc/security/limits.conf

向其添加以下行

root soft     nproc          65535    
root hard     nproc          65535   
root soft     nofile         65535   
root hard     nofile         65535

编辑以下文件

user@ubuntu:~$ sudo vim /etc/pam.d/common-session

将此行添加到其中

session required pam_limits.so

注销并登录,然后尝试以下命令

user@ubuntu:~$ ulimit -n
65535

选项3只需在以下行添加

DefaultLimitNOFILE=65535

到/etc/systemd/system.conf和/etc/systemd/user.conf

答案 5 :(得分: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);
        });
    }
};

答案 6 :(得分:2)

我不确定这是否会帮助任何人,我开始从事具有很多依赖项的大项目,这使我犯了同样的错误。我的同事建议我使用brew安装watchman,从而为我解决了这个问题。

brew update
brew install watchman

答案 7 :(得分:1)

使用风笛,您只需要更改

FS.readFile(filename, onRealRead);

=&GT;

var bagpipe = new Bagpipe(10);

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

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

答案 8 :(得分:1)

运行 nodemon 命令时遇到同样的问题,因此我减少了在 sublime text 中打开的文件的名称,错误消失了。

答案 9 :(得分: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

答案 10 :(得分:1)

使用最新的fs-extra

我在Ubuntu(16和18)上遇到了这个问题,文件/套接字描述符空间很大(用lsof |wc -l计数)。使用fs-extra8.1.0。更新为9.0.0后,“错误:EMFILE,打开的文件太多”消失了。

我在使用节点处理文件系统的各种OS上遇到了各种问题。文件系统显然并不简单。

答案 11 :(得分:0)

基于@ blak3r的答案,以下是一些速记,以防其他诊断:

如果您要调试已耗尽文件描述符的Node.js脚本,则以下一行将为您提供所涉及的节点进程使用的lsof的输出:

openFiles = child_process.execSync(`lsof -p ${process.pid}`);

这将同步运行由当前运行的Node.js进程过滤的lsof,并通过缓冲区返回结果。

然后使用console.log(openFiles.toString())将缓冲区转换为字符串并记录结果。

答案 12 :(得分:0)

如果您要部署使用Visual Studio模板(并具有web.config)创建的React解决方案,这可能会解决您的问题。在Azure发布管道中,选择模板时,使用:

Azure App Service部署

代替:

将Node.js应用部署到Azure应用服务

对我有用!

答案 13 :(得分:0)

这是我的两分钱:考虑到CSV文件只是我为 数据(字符串)而避免出现此问题的文本行。

在用例中对我来说最简单的解决方案。

它可以与优美的fs或标准fs一起使用。请注意,创建时文件中不会包含标题。

// import graceful-fs or normal fs
const fs = require("graceful-fs"); // or use: const fs = require("fs") 

// Create output file and set it up to receive streamed data
// Flag is to say "append" so that data can be recursively added to the same file 
let fakeCSV = fs.createWriteStream("./output/document.csv", {
  flags: "a",
});

和需要像这样流式传输到文件中的数据

// create custom streamer that can be invoked when needed
const customStreamer = (dataToWrite) => {
  fakeCSV.write(dataToWrite + "\n");
};

请注意,dataToWrite只是带有自定义分隔符(如“;”)的字符串。要么 ”,”。 即

const dataToWrite = "batman" + ";" + "superman"
customStreamer(dataToWrite);

这会将“ batman; superman”写入文件。


答案 14 :(得分:0)

对于可能仍在寻找解决方案的任何人,使用async-await对我来说都很好:

fs.readdir(<directory path></directory>, async (err, filenames) => {
    if (err) {
        console.log(err);
    }

    try {
        for (let filename of filenames) {
            const fileContent = await new Promise((resolve, reject) => {
                fs.readFile(<dirctory path + filename>, 'utf-8', (err, content) => {
                    if (err) {
                        reject(err);
                    }
                    resolve(content);
                });
            });
            ... // do things with fileContent
        }
    } catch (err) {
        console.log(err);
    }
});

答案 15 :(得分:0)

我确实安装了值班员,更改限制等,但在Gulp中不起作用。

重新启动iterm2确实有所帮助。

答案 16 :(得分:0)

对于 nodemon 用户: 只需使用-ignore 标志即可解决问题。

示例:

nodemon app.js --ignore node_modules/ --ignore data/

答案 17 :(得分: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));
})

答案 18 :(得分:-1)

我遇到了这个问题,我通过运行npm update来解决了这个问题。

在某些情况下,您可能需要删除node_modules rm -rf node_modules/

答案 19 :(得分:-2)

更改Node版本后可能会发生这种情况 ERR emfile 太多打开的文件

  • 重启电脑
  • brew install watchman

应该绝对解决问题