几天来,我一直在寻找一个有效的错误解决方案

错误: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);

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


当前回答

在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

其他回答

当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运行,则使用。

我自己刚刚写了一小段代码来解决这个问题,所有其他的解决方案看起来都太重量级了,需要你改变程序结构。

这个解决方案只是暂停任何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);
        });
    }
};

以@blak3r的回答为基础,以下是我使用的一些速记,以防它有助于其他诊断:

如果你试图调试一个正在耗尽文件描述符的node .js脚本,这里有一行给你问题节点进程使用的lsof的输出:

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

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

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

以下是我的观点:考虑到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

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