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

2020/10/24 10:22 · javascript ·  · 0评论

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

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服务器吗?你有什么经验?

对于当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运行,请使用。

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

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

brew update
brew install watchman

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

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

您正在读取太多文件。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()

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

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

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

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

用风笛,你只需要改变

FS.readFile(filename, onRealRead);

=>

var bagpipe = new Bagpipe(10);

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

风笛可以帮助您限制平行度。更多详细信息:https : //github.com/JacksonTian/bagpipe

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

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

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

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

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

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

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

使用最新的fs-extra

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

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

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

例:

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

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

重启iterm2实际上有所帮助。

对于可能仍在寻找解决方案的任何人,使用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);
    }
});

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

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

本文地址:http://javascript.askforanswer.com/jiedianhecuowuemfiledakaidewenjiantaiduo.html
文章标签: ,   ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!