nút và lỗi: EMFILE, quá nhiều tệp đang mở


165

Trong một số ngày, tôi đã tìm kiếm một giải pháp làm việc cho một lỗi

Error: EMFILE, too many open files

Có vẻ như nhiều người có cùng một vấn đề. Câu trả lời thông thường liên quan đến việc tăng số lượng mô tả tập tin. Vì vậy, tôi đã thử điều này:

sysctl -w kern.maxfiles=20480,

Giá trị mặc định là 10240. Điều này hơi lạ trong mắt tôi, vì số lượng tệp tôi đang xử lý trong thư mục dưới 10240. Ngay cả người lạ, tôi vẫn nhận được lỗi tương tự sau khi tôi tăng số lượng mô tả tệp .

Câu hỏi thứ hai:

Sau một số tìm kiếm, tôi thấy có một vấn đề xoay quanh vấn đề "quá nhiều tệp đang mở":

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

Thật không may, tôi vẫn nhận được lỗi tương tự. Có gì sai với mã này?

Một câu hỏi cuối cùng (tôi chưa quen với javascript và nút), tôi đang trong quá trình phát triển một ứng dụng web với rất nhiều yêu cầu cho khoảng 5000 người dùng hàng ngày. Tôi đã có nhiều năm kinh nghiệm lập trình với các ngôn ngữ khác như python và java. Vì vậy, ban đầu tôi nghĩ sẽ phát triển ứng dụng này với django hoặc play framework. Sau đó, tôi phát hiện ra nút và tôi phải nói rằng ý tưởng về mô hình I / O không chặn thực sự rất hay, quyến rũ và hầu hết đều rất nhanh!

Nhưng loại vấn đề nào tôi nên mong đợi với nút? Đây có phải là một máy chủ web đã được chứng minh? Kinh nghiệm của bạn là gì?

Câu trả lời:


83

Vì khi duyên dáng-fs không hoạt động ... hoặc bạn chỉ muốn hiểu rò rỉ đến từ đâu. Thực hiện theo quy trình này.

(ví dụ: Graceful-fs sẽ không sửa xe của bạn nếu vấn đề của bạn là về ổ cắm.)

Từ bài viết trên Blog của tôi: http://www.blakerobertson.com/devlog/2014/1/11/how-to-determine-whats-causing-error-connect-emfile-nodejs.html

Cách ly

Lệnh này sẽ xuất ra số lượng các thẻ điều khiển mở cho các quy trình của 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)
....

Lưu ý: 1023u (dòng cuối cùng) - đó là xử lý tệp thứ 1024, mức tối đa mặc định.

Bây giờ, hãy nhìn vào cột cuối cùng. Điều đó chỉ ra tài nguyên nào đang mở. Bạn có thể sẽ thấy một số dòng tất cả có cùng tên tài nguyên. Hy vọng rằng, bây giờ cho bạn biết nơi để tìm mã của bạn cho rò rỉ.

Nếu bạn không biết nhiều quy trình nút, trước tiên hãy tra cứu quy trình nào có mã 12211. Điều đó sẽ cho bạn biết quy trình.

Trong trường hợp của tôi ở trên, tôi nhận thấy rằng có một loạt các Địa chỉ IP rất giống nhau. Tất cả đều 54.236.3.### bằng cách thực hiện tra cứu địa chỉ IP, có thể xác định trong trường hợp của tôi, nó có liên quan đến pubnub.

Tham khảo lệnh

Sử dụng cú pháp này để xác định có bao nhiêu xử lý mở một quy trình đã mở ...

Để có được số lượng tệp đang mở cho một pid nhất định

Tôi đã sử dụng lệnh này để kiểm tra số lượng tệp được mở sau khi thực hiện các sự kiện khác nhau trong ứng dụng của mình.

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

Giới hạn quá trình của bạn là gì?

ulimit -a

Dòng bạn muốn sẽ như thế này:

open files                      (-n) 1024

Thay đổi vĩnh viễn giới hạn:

  • đã thử nghiệm trên Ubuntu 14.04, nodejs v. 7.9

Trong trường hợp nếu bạn muốn mở nhiều kết nối (websockets là một ví dụ điển hình), bạn có thể tăng giới hạn vĩnh viễn:

  • tệp: /etc/pam.d/common-session (thêm vào cuối)

    session required pam_limits.so
  • tệp: /etc/security/limits.conf (thêm vào cuối hoặc chỉnh sửa nếu đã tồn tại)

    root soft  nofile 40000
    root hard  nofile 100000
  • khởi động lại nodejs của bạn và đăng xuất / đăng nhập từ ssh.

  • điều này có thể không hoạt động đối với NodeJS cũ hơn, bạn sẽ cần khởi động lại máy chủ
  • sử dụng thay vì nếu nút của bạn chạy với uid khác nhau.

1
Làm thế nào bạn có thể thay đổi giới hạn tập tin mở?
Om3ga

13
ulimit -n 2048 để cho phép 2048 tệp được mở
Gaël Barbin

1
Đây là câu trả lời mô tả và chính xác nhất. Cảm ơn bạn!
Kostanos

Tôi có số hiếm. lsof -i -n -P | grep "12843" | wc -l== 4085 nhưng ulimit -a | grep "open files"== (-n) 1024 bất kỳ manh mối nào làm thế nào tôi có thể có nhiều tệp được mở hơn giới hạn tối đa?
Kostanos

1
Vì blog của @ blak3r dường như không hoạt động, đây là một liên kết đến bài viết của anh ấy trên máy quay ngược. web.archive.org/web/20140508165434/http:// Cấp Siêu hữu ích và đọc thực sự tuyệt vời!
James

72

Sử dụng graceful-fsmô-đun của Isaac Schlueter (duy trì node.js) có lẽ là giải pháp thích hợp nhất. Nó thực hiện sao lưu tăng dần nếu gặp phải EMFILE. Nó có thể được sử dụng như là một thay thế thả vào cho fsmô-đun tích hợp.


2
Cứu tôi với, tại sao đây không phải là nút mặc định? Tại sao tôi cần cài đặt một số plugin của bên thứ 3 để giải quyết vấn đề?
Anthony Webb

7
Tôi nghĩ, nói chung, Node cố gắng tiết lộ càng nhiều cho người dùng càng tốt. Điều này mang đến cho tất cả mọi người (không chỉ các nhà phát triển cốt lõi của Node) cơ hội để giải quyết bất kỳ vấn đề nào phát sinh từ việc sử dụng giao diện tương đối thô này. Đồng thời, thật dễ dàng để xuất bản các giải pháp và tải xuống các giải pháp được xuất bản bởi những người khác qua npm. Đừng mong đợi nhiều thông minh từ chính Node. Thay vào đó, mong đợi để tìm thấy thông minh trong các gói được xuất bản vào npm.
Myrne Stol

5
Điều đó tốt nếu đó là mã của riêng bạn, nhưng nhiều mô-đun npm không sử dụng mã này.
UpTheCux

1
Mô-đun này đã giải quyết tất cả các vấn đề của tôi! Tôi đồng ý rằng nút có vẻ hơi thô, nhưng chủ yếu là vì thật khó để hiểu điều gì đang xảy ra với quá ít tài liệu và chấp nhận các giải pháp đúng cho các vấn đề đã biết.
sidonaldson

Làm thế nào để bạn npm nó? Làm cách nào để kết hợp mã này trong mã của tôi thay vì fs thông thường?
Aviram Netanel

11

Tôi không chắc liệu điều này có giúp được ai không, tôi bắt đầu thực hiện một dự án lớn với rất nhiều phụ thuộc khiến tôi mắc lỗi tương tự. Đồng nghiệp của tôi đề nghị tôi cài đặt watchmanbằng brew và điều đó đã khắc phục vấn đề này cho tôi.

brew update
brew install watchman

Chỉnh sửa vào ngày 26 tháng 6 năm 2019: Liên kết Github với watchman


Điều này đã giúp tôi ít nhất. Trong một dự án có nguồn gốc phản ứng, trình đóng gói có thể mở các tệp nguyên gốc hoặc (nếu nó được cài đặt) sử dụng watchman để thực hiện theo cách đẹp hơn cho hệ điều hành. Vì vậy, nó có thể là một trợ giúp lớn - nó được ghi lại trong phần khởi động nhanh CLI có nguồn gốc phản ứng cho macOS: facebook.github.io/react-native/docs/getting-started.html - chúc mừng!
Mike Hardy

7

Tôi gặp phải vấn đề này ngày hôm nay và không tìm được giải pháp tốt nào cho nó, tôi đã tạo ra một mô-đun để giải quyết nó. Tôi đã được truyền cảm hứng bởi đoạn trích của @ fbartho, nhưng muốn tránh ghi đè lên mô-đun fs.

Mô-đun tôi đã viết là Filequeue và bạn sử dụng nó giống như 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
        }
    });
});

7

Bạn đang đọc quá nhiều tập tin. Nút đọc các tệp không đồng bộ, nó sẽ đọc tất cả các tệp cùng một lúc. Vì vậy, có lẽ bạn đang đọc giới hạn 10240.

Xem nếu điều này hoạt động:

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

6

Giống như tất cả chúng ta, bạn là nạn nhân khác của I / O không đồng bộ. Với các cuộc gọi không đồng bộ, nếu bạn lặp xung quanh nhiều tệp, Node.js sẽ bắt đầu mở một bộ mô tả tệp cho mỗi tệp để đọc và sau đó sẽ đợi hành động cho đến khi bạn đóng nó.

Bộ mô tả tệp vẫn mở cho đến khi tài nguyên có sẵn trên máy chủ của bạn để đọc nó. Ngay cả khi các tệp của bạn nhỏ và đọc hoặc cập nhật nhanh, sẽ mất một chút thời gian, nhưng đồng thời, vòng lặp của bạn không dừng để mở mô tả tệp mới. Vì vậy, nếu bạn có quá nhiều tệp, giới hạn sẽ sớm đạt được và bạn sẽ có được một EMFILE đẹp .

Có một giải pháp, tạo ra một hàng đợi để tránh hiệu ứng này.

Nhờ những người đã viết Async , có một chức năng rất hữu ích cho việc đó. Có một phương pháp gọi là Async.queue , bạn tạo một hàng đợi mới với giới hạn và sau đó thêm tên tệp vào hàng đợi.

Lưu ý: Nếu bạn phải mở nhiều tệp, bạn nên lưu trữ tệp nào đang mở và không mở lại chúng vô hạn.

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

Bạn có thể thấy rằng mỗi tệp được thêm vào hàng đợi (tên tệp console.log), nhưng chỉ khi hàng đợi hiện tại nằm dưới giới hạn bạn đã đặt trước đó.

async.queue nhận thông tin về tính khả dụng của hàng đợi thông qua một cuộc gọi lại, cuộc gọi lại này chỉ được gọi khi tệp dữ liệu được đọc và bất kỳ hành động nào bạn phải làm là đạt được. (xem phương thức fileRead)

Vì vậy, bạn không thể bị choáng ngợp bởi mô tả tập tin.

> 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

3

Tôi vừa viết xong một đoạn mã nhỏ để tự giải quyết vấn đề này, tất cả các giải pháp khác có vẻ quá nặng nề và yêu cầu bạn thay đổi cấu trúc chương trình của mình.

Giải pháp này chỉ thực hiện bất kỳ cuộc gọi fs.readFile hoặc fs.writeFile nào để không có nhiều hơn một số được đặt trong chuyến bay tại bất kỳ thời điểm nào.

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

Bạn nên tạo một repo cho điều này trên github.
Nick

Điều này hoạt động rất tốt nếu duyên dáng-fs không làm việc cho bạn.
Ceekay

3

Tôi đã làm tất cả những thứ ở trên được đề cập cho cùng một vấn đề nhưng không có gì hiệu quả. Tôi đã thử dưới đây nó hoạt động 100%. Thay đổi cấu hình đơn giản.

Tùy chọn 1 đặt giới hạn (Nó sẽ không hoạt động hầu hết thời gian)

user@ubuntu:~$ ulimit -n 65535

kiểm tra giới hạn có sẵn

user@ubuntu:~$ ulimit -n
1024

Tùy chọn 2 Để tăng giới hạn khả dụng để nói 65535

user@ubuntu:~$ sudo nano /etc/sysctl.conf

thêm dòng sau vào nó

fs.file-max = 65535

chạy cái này để làm mới với cấu hình mới

user@ubuntu:~$ sudo sysctl -p

chỉnh sửa tập tin sau

user@ubuntu:~$ sudo vim /etc/security/limits.conf

thêm các dòng sau vào nó

root soft     nproc          65535    
root hard     nproc          65535   
root soft     nofile         65535   
root hard     nofile         65535

chỉnh sửa tập tin sau

user@ubuntu:~$ sudo vim /etc/pam.d/common-session

thêm dòng này vào nó

session required pam_limits.so

đăng xuất và đăng nhập và thử lệnh sau

user@ubuntu:~$ ulimit -n
65535

Tùy chọn 3 Chỉ cần thêm dòng dưới đây vào

DefaultLimitNOFILE=65535

đến /etc/systemd/system.conf và /etc/systemd/user.conf


tùy chọn 2 khá dài và hy vọng tùy chọn 3 hoạt động, nhưng nó không dành cho Ubuntu 18 của tôi
eugene

1

Với bagpipe, bạn chỉ cần thay đổi

FS.readFile(filename, onRealRead);

=>

var bagpipe = new Bagpipe(10);

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

Bagpipe giúp bạn hạn chế song song. biết thêm chi tiết: https://github.com/JacksonTian/bagpipe


Đó là tất cả về ngôn ngữ Trung Quốc hoặc châu Á khác. Có tài liệu nào viết bằng tiếng Anh không?
Fatih Arslan

Tài liệu tiếng Anh @FatihArslan hiện có sẵn.
dùng1837639

1

Có vấn đề tương tự khi chạy lệnh gật đầu vì vậy tôi đã giảm tên của các tệp được mở trong văn bản cao siêu và lỗi đã biến mất.


Tôi cũng vậy, đã nhận được EMFILElỗi và thông qua thử nghiệm và lỗi nhận thấy rằng việc đóng một số cửa sổ Sublime đã giải quyết vấn đề. Tôi vẫn không biết tại sao. Tôi đã thử thêm ulimit -n 2560vào .bash_profile của mình nhưng điều đó không giải quyết được vấn đề. Có phải điều này cho thấy cần phải thay đổi thành Atom thay thế?
Qodesmith

1

Dựa trên câu trả lời của @ blak3r, đây là một chút tốc ký mà tôi sử dụng trong trường hợp nó giúp chẩn đoán khác:

Nếu bạn đang cố gắng gỡ lỗi một tập lệnh Node.js sắp hết bộ mô tả tệp thì đây là một dòng để cung cấp cho bạn đầu ra lsofđược sử dụng bởi quy trình nút được đề cập:

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

Điều này sẽ chạy đồng bộ lsofđược lọc theo quy trình Node.js đang chạy hiện tại và trả về kết quả qua bộ đệm.

Sau đó sử dụng console.log(openFiles.toString())để chuyển đổi bộ đệm thành một chuỗi và ghi lại kết quả.


0

cwait là một giải pháp chung để hạn chế thực thi đồng thời bất kỳ chức năng nào trả lại lời hứa.

Trong trường hợp của bạn, mã có thể là một cái gì đó như:

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

0

Đối với nodemon người sử dụng: Chỉ cần sử dụng --ignore cờ để giải quyết vấn đề.

Thí dụ:

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

0

Sử dụng mới nhất fs-extra.

Tôi gặp vấn đề đó vào Ubuntu(16 và 18) với nhiều không gian mô tả tệp / ổ cắm (tính bằng lsof |wc -l). fs-extraPhiên bản đã qua sử dụng 8.1.0. Sau khi cập nhật 9.0.0"Lỗi: EMFILE, quá nhiều tệp đang mở" đã biến mất.

Tôi đã gặp sự cố khác nhau trên hệ điều hành khác nhau 'với các hệ thống tệp xử lý nút. Hệ thống tập tin rõ ràng là không tầm thường.


0

Tôi đã có vấn đề này, và tôi đã giải quyết nó bằng cách chạy npm updatevà nó đã hoạt động.

Trong một số trường hợp, bạn có thể cần phải xóa node_modules rm -rf node_modules/

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.