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

node and Error: EMFILE, too many open files

本文关键字:文件 太多 错误 EMFILE 节点      更新时间:2023-09-26

几天来,我一直在寻找错误的工作解决方案

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

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

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

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

当优雅的 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 就是一个很好的例子(,您可以永久增加限制:

  • 文件:/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

  • 使用
  • 代替,如果您的节点使用不同的 UI 运行。

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

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

brew update
brew install watchman

2019年6月26日编辑:Github链接到watchman

我为同一个问题做了上面提到的所有事情,但没有任何效果。我在下面尝试了它的工作 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

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

您正在读取太多文件。节点异步读取文件,它将一次读取所有文件。因此,您可能正在阅读 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");
    });
}

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

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

因此,您不会被文件描述符淹没。

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

我通过更新守望者解决了这个问题

 brew install watchman

有了风笛,你只需要改变

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的答案,这里有一些我使用的速记,以防它有助于其他诊断:

如果您尝试调试的文件描述符用完.js Node 脚本,这里有一行为您提供相关节点进程使用的lsof的输出:

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

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

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

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

例:

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

使用最新的fs-extra

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

我在节点处理文件系统的不同操作系统上遇到了各种问题。文件系统显然不是微不足道的。

我安装了守望者,更改了限制等,但在 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);
    }
});

这是我的两分钱: 考虑到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);

这会将"蝙蝠侠;超人"写入文件。

<小时 />
  • 请注意,此示例中没有错误捕获或任何内容。
  • 文档:https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options

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

Azure 应用服务部署

而不是:

将节点.js应用部署到 Azure 应用服务

它对我有用!

到目前为止,还有

另一种可能性尚未在任何答案中考虑或讨论:符号链接循环

Node的递归文件系统观察器似乎无法检测和处理符号链接的循环。因此,您只需运行以下命令,即可使用任意高的nfiles ulimit 轻松触发此错误:

mkdir a
mkdir a/b
cd a/b 
ln -s .. c

GNU find会注意到符号链接循环并中止:

$ find a -follow
a
a/b
find: File system loop detected; ‘a/b/c’ is part of the same file system loop as ‘a’.

但节点不会。如果您在树上设置了监视,它将引发EMFILE, too many open files错误。

除其他事项外,这可能发生在存在包含关系的node_modules

parent/
  package.json
  child/
    package.json

这就是我在尝试构建的项目中遇到它的方式。

请注意,您不一定需要使此问题过于复杂,重试即可。

import { promises as fs } from "fs";
const filepaths = [];
const errors = [];
function process_file(content: string) {
    // logic here
}
await Promise.all(
    filepaths.map(function read_each(filepath) {
        return fs
            .readFile(filepath, "utf8")
            .then(process_file)
            .catch(function (error) {
                if (error.code === "EMFILE") return read_each(filepath);
                else errors.push({ file: filepath, error });
            });
    }),
);

你所做的几乎是正确的:

sysctl -w kern.maxfiles=20480

在我的macOS上,默认值为491520,您设置的值实际上低于我的系统默认值,我只是将其设置为999999并且效果很好。没有更多的错误。

编辑:我忘了提到之后重新启动。

希望这有帮助。

我遇到了这个问题,我通过运行npm update解决了它并且它起作用了。

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

在Windows上,似乎没有ulimit命令来增加打开的文件数量。在graceful-fs中,它维护一个队列来运行I/O操作,例如:读/写文件。

但是,fs.readFile, fs.writeFile是基于 fs.open ,因此您需要手动打开/关闭文件来解决此错误。

import fs from 'fs/promises';
const fd = await fs.open('path-to-file', 'r');
await fd.readFile('utf-8'); // <== read through file handle
await fd.close();           // <== manually close it

更改节点版本后可能会发生这种情况ERR 归档打开的文件过多

  • 重新启动计算机
  • 酿造安装守望者

应该绝对解决问题

首先使用 expo update 更新您的 expo 版本,然后运行 yarn / npm install 。这为我解决了问题!