Node и ошибка: EMFILE, слишком много открытых файлов

В течение нескольких дней я искал рабочее решение для ошибки

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), я занимаюсь разработкой сети приложение с большим количеством запросов для около 5000 ежедневных пользователей. У меня многолетний опыт работы в программирование на других языках, таких как python и java. поэтому изначально я решил разработать это приложение с помощью django или play framework. Затем я обнаружил node, и я должен сказать, что идея неблокирующей модели ввода-вывода действительно хорошая, соблазнительная и, самое главное, очень быстро!

Но какие проблемы я должен ожидать от node? Является ли это проверенным продуктом веб-сервером? Каковы ваши впечатления?

Ответ 1

Если 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-го файла, который является максимальным по умолчанию.

Теперь посмотрим на последний столбец. Это указывает, какой ресурс открыт. Вероятно, вы увидите несколько строк с одинаковым именем ресурса. Надеюсь, теперь это говорит вам, где искать в своем коде для утечки.

Если вы не знаете несколько процессов node, сначала посмотрите, какой процесс имеет 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

В случае, если вы ожидаете открыть много соединений (например, веб-сокеты), вы можете постоянно увеличить лимит:

  • file:/etc/pam.d/common-session (добавить в конец)

    session required pam_limits.so
    
  • file:/etc/security/limits.conf(добавьте в конец или отредактируйте, если он уже существует)

    root soft  nofile 40000
    root hard  nofile 100000
    
  • перезапустите ваши узлы и выйдите из системы ssh.

  • это может не работать для более старого узла NodeJS, вам необходимо перезагрузить сервер.
  • а не если ваш node работает с другим uid.

Ответ 2

Использование модуля graceful-fs Исаака Шлютера (node.js supporter), вероятно, является наиболее подходящим решением. При возникновении EMFILE происходит постепенное отключение. Его можно использовать как замену для встроенного модуля fs.

Ответ 3

Сегодня я столкнулся с этой проблемой и не нашел хороших решений для этого, я создал модуль для его решения. Я был вдохновлен фрагментом @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
        }
    });
});

Ответ 4

Вы читаете слишком много файлов. Узел читает файлы асинхронно, он будет читать все файлы одновременно. Итак, вы, вероятно, читаете предел 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()

Ответ 5

Я только что закончил писать небольшой фрагмент кода для решения этой проблемы самостоятельно, все другие решения выглядят слишком тяжело и требуют изменения вашей структуры программы.

Это решение просто останавливает любые вызовы 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);
        });
    }
};

Ответ 6

Я не уверен, поможет ли это кому-нибудь, я начал работать над большим проектом с большим количеством зависимостей, который выдал мне ту же ошибку. Мой коллега предложил мне установить watchman используя brew, и это решило эту проблему для меня.

brew update
brew install watchman

Редактировать 26 июня 2019 года: Github ссылка на сторожа

Ответ 7

С волынкой вам просто нужно изменить

FS.readFile(filename, onRealRead);

= >

var bagpipe = new Bagpipe(10);

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

Волынка помогает вам ограничить параллель. подробнее: https://github.com/JacksonTian/bagpipe

Ответ 8

Имела ту же проблему при запуске команды nodemon, поэтому я уменьшил имя файлов, открытых в возвышенном тексте, и ошибка исчезла.

Ответ 9

Как и все мы, вы являетесь еще одной жертвой асинхронного ввода-вывода. При асинхронных вызовах, если вы зациклились на большом количестве файлов, Node.js начнет открывать файловый дескриптор для каждого файла для чтения, а затем будет ждать действия, пока вы его не закроете.

Файловый дескриптор остается открытым, пока на вашем сервере не появится ресурс для его чтения. Даже если ваши файлы небольшие, а чтение или обновление выполняется быстро, это займет некоторое время, но в то же время ваш цикл не останавливается, чтобы открыть дескриптор новых файлов. Так что, если у вас слишком много файлов, предел будет скоро достигнут, и вы получите красивый ЭМФИЛЬ.

Есть одно решение - создать очередь, чтобы избежать этого эффекта.

Спасибо людям, которые написали 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

Ответ 10

cwait является общим решением для ограничения одновременных исполнений любых функций, возвращающих promises.

В вашем случае код может выглядеть примерно так:

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

Ответ 11

Опираясь на ответ @blak3r, я приведу здесь несколько сокращений, которые помогут другим диагностировать:

Если вы пытаетесь отладить скрипт Node.js, в котором заканчиваются файловые дескрипторы, то здесь есть строка, чтобы дать вам вывод lsof используемый рассматриваемым процессом узла:

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

Это синхронно запустит lsof отфильтрованный текущим запущенным процессом Node.js, и вернет результаты через буфер.

Затем используйте console.log(openFiles.toString()) чтобы преобразовать буфер в строку и записать результаты.