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


164

几天以来,我一直在寻找错误的可行解决方案

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和node的新手),我正在开发一个Web应用程序,该应用程序每天有大约5000个用户,并且有很多请求。我在使用其他语言(例如python和java)进行编程方面有多年的经验。所以最初我想用Django或play框架来开发此应用程序。然后我发现了节点,我必须说,非阻塞I / O模型的想法真的很好,很诱人,而且所有这些都非常快!

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

Answers:


83

对于当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个文件句柄,这是默认的最大值。

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

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

在上述情况下,我注意到有一堆非常相似的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是一个很好的例子),则可以永久增加该限制:

  • 文件:/etc/pam.d/common-session (添加到末尾)

    session required pam_limits.so
  • 文件:/etc/security/limits.conf (添加到末尾,或者如果已经存在则进行编辑)

    root soft  nofile 40000
    root hard  nofile 100000
  • 重新启动您的nodejs并从ssh注销/登录。

  • 这对于较旧的NodeJS可能不起作用,您需要重新启动服务器
  • 如果您的节点使用不同的uid运行,请使用。

1
如何更改打开文件的限制?
Om3ga 2014年

13
的ulimit -n 2048至2048个允许打开的文件
盖尔Barbin

1
这是最具描述性和正确性的答案。谢谢!
Kostanos

我的电话很少。lsof -i -n -P | grep "12843" | wc -l== 4085ulimit -a | grep "open files"== (-n)1024任何提示我怎么可能有比最大限制更多的打开文件?
Kostanos

1
由于@ blak3r的博客似乎已停刊,因此以下是他在回溯机器上的文章的链接。web.archive.org/web/20140508165434/http://… 超级有帮助,真的很棒!
詹姆斯

72

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


2
救了我,为什么这不是默认节点?为什么我需要安装一些第三方插件才能解决此问题?
Anthony Webb

7
我认为,通常来说,Node会尝试向用户尽可能多地公开。这使每个人(不仅仅是Node核心开发人员)都有机会解决由于使用此相对原始的接口而引起的任何问题。同时,发布解决方案并通过npm下载其他人发布的解决方案确实很容易。不要指望Node本身有很多聪明人。相反,期望在npm上发布的软件包中找到这些聪明人。
Myrne Stol 2013年

5
如果这是您自己的代码,那很好,但是很多npm模块不使用它。
UpTheCreek

1
这个模块解决了我所有的问题!我同意节点似乎还有些生硬,但是主要是因为用很少的文档和已知问题的正确解决方案很难理解出了什么问题。
sidonaldson

你怎么npm呢?如何在我的代码中而不是常规的fs中合并呢?
Aviram Netanel 2014年

11

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

brew update
brew install watchman

编辑于2019年6月26日: Github到守望者的链接


这至少对我有帮助。在本机项目中,捆绑程序可以本地打开文件,也可以(如果已安装)使用守卫者以对操作系统更好的方式来进行操作。所以这可能是一个很大的帮助-甚至在macOS的react-native CLI快速入门中也有记录:facebook.github.io/react-native/docs/getting-started.html-欢呼!
Mike Hardy

7

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

7

您正在读取太多文件。Node异步读取文件,它将立即读取所有文件。因此,您可能正在读取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()

6

像我们所有人一样,您是异步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

3

我刚刚写了一些代码片段来自己解决这个问题,所有其他解决方案似乎都太笨重了,需要您更改程序结构。

此解决方案只会使任何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);
        });
    }
};

你应该在github上为此做一个仓库。
尼克

如果graceful -fs不适用于您,则此方法非常有效。
Ceekay

3

我针对相同的问题做了上述所有工作,但没有任何效果。我尝试在它下面工作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


选项2相当长,希望选项3可以工作,但不适用于我的ubuntu 18
尤金


1

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


我也遇到了EMFILE错误,并且通过反复试验发现关闭某些Sublime窗口可以解决此问题。我仍然不知道为什么。我尝试添加ulimit -n 2560到.bash_profile中,但这并不能解决问题。这是否表明需要改为使用Atom
The Qodesmith '16

1

以@ blak3r的答案为基础,这是我使用的一些速记方式,以防其他诊断:

如果您要调试已用完文件描述符的Node.js脚本,则以下一行可以为您提供lsof有关节点进程使用的输出:

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

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

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


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

0

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

例:

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

0

使用最新的fs-extra

我在Ubuntu(16和18)上有大量文件/套接字描述符空间(用计数lsof |wc -l)的问题。使用fs-extra版本8.1.0。更新为9.0.0“错误:EMFILE,打开的文件过多”后,消失了。

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


0

我遇到了这个问题,并且我通过运行解决了该问题,并且npm update工作正常。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.