几天来,我一直在寻找一个有效的错误解决方案
错误: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);
不幸的是,我仍然收到相同的错误。
这段代码有什么问题?
你读的文件太多了。节点异步读取文件,它会一次读取所有文件。所以你可能读到了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()
我自己刚刚写了一小段代码来解决这个问题,所有其他的解决方案看起来都太重量级了,需要你改变程序结构。
这个解决方案只是暂停任何f。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);
});
}
};
我今天遇到了这个问题,没有找到好的解决方案,我创建了一个模块来解决它。我受到@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
}
});
});
当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相关的。
命令参考
使用此语法确定一个进程有多少个打开句柄…
获取某个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(添加到结尾)
会话需要pam_limits.so
文件:/etc/security/limits.conf(添加到末尾,如果已经存在,则编辑)
根软nofile 40000
根硬nofile 100000
重新启动nodejs,从ssh注销/登录。
这可能不适用于旧的NodeJS,你需要重新启动服务器
如果您的节点使用不同的uid运行,则使用。
像我们所有人一样,您也是异步I/O的另一个受害者。对于异步调用,如果你循环了很多文件,Node.js将开始为每个要读取的文件打开一个文件描述符,然后等待操作,直到关闭它。
文件描述符保持打开状态,直到服务器上的资源可用来读取它。即使您的文件很小,读取或更新很快,也需要一些时间,但与此同时,循环不会停止打开新的文件描述符。所以如果你有太多的文件,限制将很快达到,你会得到一个漂亮的EMFILE。
有一个解决方案,创建一个队列来避免这种影响。
感谢编写Async的人,这里有一个非常有用的函数。有一个方法叫做Async。队列,您将创建一个具有限制的新队列,然后将文件名添加到队列中。
注意:如果你必须打开许多文件,最好存储当前打开的文件,不要无限地重新打开它们。
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文件名)中,但仅当当前队列低于您之前设置的限制时。
异步。队列通过回调获取关于队列可用性的信息,此回调仅在读取数据文件并且实现必须执行的任何操作时调用。(参见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
以@blak3r的回答为基础,以下是我使用的一些速记,以防它有助于其他诊断:
如果你试图调试一个正在耗尽文件描述符的node .js脚本,这里有一行给你问题节点进程使用的lsof的输出:
openFiles = child_process.execSync(`lsof -p ${process.pid}`);
这将同步运行由当前运行的Node.js进程过滤的lsof,并通过缓冲区返回结果。
然后使用console.log(openFiles.toString())将缓冲区转换为字符串并记录结果。
对于同一个问题,我做了上面提到的所有事情,但都不起作用。我试了下,它工作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
对于那些仍然在寻找解决方案的人来说,使用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);
这将向文件写入“batman;superman”。
请注意,在这个示例中没有错误捕获或其他任何东西。
文档:https://nodejs.org/api/fs.html # fs_fs_createwritestream_path_options
还有一种可能性,到目前为止,在任何答案中都没有考虑或讨论过:符号链接循环。
节点的递归文件系统监控器似乎无法检测和处理符号链接的循环。所以你可以很容易地用任意高的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,太多打开文件的错误。
在有包含关系的node_modules中也会发生这种情况:
parent/
package.json
child/
package.json
这也是我在做项目时遇到的问题。
在Windows上,似乎没有ulimit命令来增加打开文件的数量。在graceful-fs中,它维护一个队列来运行I/O操作,例如:读/写文件。
然而,fs。readFile, fs。writeFile是基于fs的。打开,因此您需要手动打开/关闭文件来解决此错误。
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