几天来,我一直在寻找一个有效的错误解决方案
错误: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);
不幸的是,我仍然收到相同的错误。
这段代码有什么问题?
还有一种可能性,到目前为止,在任何答案中都没有考虑或讨论过:符号链接循环。
节点的递归文件系统监控器似乎无法检测和处理符号链接的循环。所以你可以很容易地用任意高的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
这也是我在做项目时遇到的问题。
当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运行,则使用。
我今天遇到了这个问题,没有找到好的解决方案,我创建了一个模块来解决它。我受到@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
}
});
});
像我们所有人一样,您也是异步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