簡體   English   中英

為什么我的NodeJS腳本在fs.readFile和fs.appendFile處理大量文件的過程中停頓下來。

[英]Why does my NodeJS script bog down during fs.readFile and fs.appendFile handling large numbers of files.

我有一個需要打開約120k HTML頁面的文件夾(每個文件約為70kb),使用xPath解析一些數據並將該數據附加到.csv文件中。

下面是我的代碼:

它應該從parseFolder中讀取文件列表,遍歷每個文件名,然后使用fs.readFile打開文件,然后使用jsdom和xpath解析數據,並使用fs.appendFile將其保存到csv文件中。

對於前100個左右的文件來說,這似乎做得很好,但是之后會逐漸變慢,消耗內存和CPU,最終停滯。 我有16 gigs的內存,當我的內存使用量達到約7gigs時,似乎達到了一些極限。

我是JS和Node的新手,對我所缺少的任何幫助將不勝感激。

var fs = require('fs');
var jsdom = require('jsdom').jsdom;
var xpath = require('xpath');
var S = require('string');
var os = require('os');

ParserRules = {
    saveFile: 'output.csv',
    parseFolder: '/a/folder/with/120k/HTML/files',
    fields: {
        "field1": "//div[@class='field1']/text()",
    }
};

start();

function start() {
    console.log('Starting...');
    fs.readdir(ParserRules.parseFolder, iterateFiles);
}

function iterateFiles(err, filesToParse) {
    for (var i = 0; i < filesToParse.length; i++) {
        file = ParserRules.parseFolder + '/' + filesToParse[i];
        console.log('Beginning read of ' + file);
        fs.readFile(file, {encoding: 'utf8'}, parseFile);
    }
}

function parseFile(err, data) {
    if (err == null) {
        var jsdomDocument = jsdom(data);
        var document = jsdomDocument.parentWindow.document;
        getContent(document);
    }
}

function getContent(document) {
    fields = ParserRules.fields;
    var csvRow = [];
    for (var field in fields) {
        try {
            console.log('Looking for ' + field);
            var nodes = xpath.select(fields[field], document);
            for (var i = 0; i < nodes.length; i++) {
                csvRow.push(getValue(nodes[i]));
            }
        } catch (err) {
            console.log(err);
        }
    }
    saveToCsv(csvRow, ParserRules.saveFile);
}

function getValue(node) {
    if(node.nodeValue != null) {
        toReturn = node.nodeValue;
    } else {
        newNode = $(node);
        toReturn = newNode.html();
    }
    return toReturn;
}

function saveToCsv(object, filePath) {
    console.log('Saving...');
    if(object.length > 0) {
        console.log('Row Exists, Saving...');
        toString = S(object).toCSV().s + os.EOL;
        fs.appendFile(filePath, toString, {encoding: 'utf8', flag: 'a'}, function(err){
            if (err) {
                console.log('Write Error: ' + err);
            } else {
                console.log('Saved ' + object);
            }
        });
    }
}

Node.js異步工作。

問題

因此,代碼的結構方式會發生:

  1. 函數iterateFiles發出120k fs.readFile調用,這導致Node.js將120k文件系統讀取操作排隊。

  2. 讀取操作完成后,Node.js將調用fs.readFile的120k回調,並且每個回調都會發出fs.appendFile操作,這將導致Node.js將120k的文件系統寫操作排隊。

  3. 最終,Node.js將調用傳遞給fs.appendFile的120k回調。 完成這些寫入操作之前,Node.js 必須掛在要寫入的數據上。

解決方案

對於這樣的任務,我建議使用fs調用的同步版本: fs.readFileSyncfs.appendFileSync

在為Web服務器編寫代碼或以某種方式由事件驅動時,您不想使用這些調用的同步版本,因為它們會導致您的應用程序阻塞。 但是,如果您編寫的代碼正在對數據進行批處理(例如,代碼操作類似於Shell腳本),則使用這些調用的同步版本會更簡單。

插圖

以下代碼是代碼的簡化模型,並說明了該問題。 它設置為從/tmp讀取,因為與任何文件一樣,它也是一個很好的文件源。 如果文件為空,我還將它設置為避免執行除parseFile之外的任何其他工作。

var fs = require('fs');

var ParserRules = {
    saveFile: 'output.csv',
    parseFolder: '/tmp'
};

start();

function start() {
    console.log('Starting...');
    fs.readdir(ParserRules.parseFolder, iterateFiles);
}

function iterateFiles(err, filesToParse) {
    for (var i = 0; i < filesToParse.length; i++) {
        var file = ParserRules.parseFolder + '/' + filesToParse[i];
        console.log('Beginning read of file number ' + i);
        fs.readFile(file, {encoding: 'utf8'}, parseFile);
    }
}

var parse_count = 0;
function parseFile(err, data) {
    if (err)
        return;

    if (data.length) {
        console.log("Parse: " + parse_count++);
        getContent(data);
    }
}

function getContent(data) {
    saveToCsv(data, ParserRules.saveFile);
}

var save_count = 0;
function saveToCsv(data, filePath) {
    fs.appendFile(filePath, data, {encoding: 'utf8', flag: 'a'},
                  function(err){
        if (err) {
            console.log('Write Error: ' + err);
        } else {
            console.log('Saved: ' + save_count++);
        }
    });
}

如果運行此代碼,您將看到所有Parse:消息連續出現。 然后, 僅在輸出所有Parse:消息之后 ,您才獲得Saved:消息。 所以您會看到類似以下內容:

Beginning read of file number N
Beginning read of file number N+1
Parse: 0
Parse: 1
... more parse messages ...
Parse: 18
Parse: 19
Saved: 0
Saved: 1
... more saved messages...
Saved: 18
Saved: 19

這說明您是在解析所有文件之后才開始保存Node。 由於Node在知道不會再次使用文件之前無法釋放與文件關聯的數據-在這種情況下,這意味着直到文件被保存--然后在某個時候Node將至少花費120,000 * 70kb的內存可容納所有文件中的所有數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM