簡體   English   中英

無法使用 Node.js 將大量數據填充到 mongodb

[英]Can't populate big chunk of data to mongodb using Node.js

我被要求導入從全市許多站點收集的大量天氣數據。 每個站點都有一台帶有一個文件夾的計算機,每 5 分鍾同步到一個中央服務器。 每天都會創建一個新文件。 所以,基本上結構是這樣的。 一個 txt 文件的格式為 csv 文件,其中第一行為字段,其余為數字。

服務器上的文件夾
|__ 站點 1 __ 日期 1.txt
| |__ date2.txt
|
|__ 站點 2 __ 日期 1.txt
|__ date2.txt
我編寫了一個小的 node.js 應用程序來將這些數據填充到 mongoDB。 但是,目前我們只有 3 個站點,但每個站點有近 900 個 txt 文件,每個文件包含 24*20 = 288 行(因為每 5 分鍾記錄一次數據)。 我嘗試運行 node 應用程序,但在讀取第一個文件夾的大約 100 個文件后,程序崩潰並顯示有關內存分配失敗的錯誤。

我嘗試了很多方法來改善這一點:

  1. 將 nodejs 的內存大小增加到 8GB => 更好一點,讀入更多文件但仍然無法移動到下一個文件夾。
  2. 在 _.forEach 循環結束時將一些變量設置為 null 和 undefined (我使用下划線)=> 沒有幫助。
  3. 移動文件數組(使用 fs.readdir),這樣第一個元素將被刪除 => 也無濟於事。

有沒有什么辦法可以強制js每次讀完一個文件就清理內存? 謝謝

更新 1:我最終在每個文件夾中一次添加了 100 個文件。 這似乎很乏味,但它奏效了,這就像一次性工作。 但是,我仍然想為此找到解決方案。

嘗試使用而不是將每個文件加載到內存中。

我已經向您發送了一個使用流和異步 i/o 實現的拉取請求

這是大部分:

var Async = require('async');
var Csv = require('csv-streamify');
var Es = require('event-stream');
var Fs = require('fs');
var Mapping = require('./folder2siteRef.json');
var MongoClient = require('mongodb').MongoClient;

var sourcePath = '/hnet/incoming/' + new Date().getFullYear();

Async.auto({
  db: function (callback) {
    console.log('opening db connection');
    MongoClient.connect('mongodb://localhost:27017/test3', callback);
  },
  subDirectory: function (callback) {
    // read the list of subfolder, which are sites
    Fs.readdir(sourcePath, callback);
  },
  loadData: ['db', 'subDirectory', function (callback, results) {
    Async.each(results.subDirectory, load(results.db), callback);
  }],
  cleanUp: ['db', 'loadData', function (callback, results) {
    console.log('closing db connection');
    results.db.close(callback);
  }]
}, function (err) {
  console.log(err || 'Done');
});

var load = function (db) {
  return function (directory, callback) {
    var basePath = sourcePath + '/' + directory;
    Async.waterfall([
      function (callback) {
        Fs.readdir(basePath, callback); // array of files in a directory
      },
      function (files, callback) {
        console.log('loading ' + files.length + ' files from ' + directory);
        Async.each(files, function (file, callback) {
          Fs.createReadStream(basePath + '/' + file)
            .pipe(Csv({objectMode: true, columns: true}))
            .pipe(transform(directory))
            .pipe(batch(200))
            .pipe(insert(db).on('end', callback));
        }, callback);
      }
    ], callback);
  };
};

var transform = function (directory) {
  return Es.map(function (data, callback) {
    data.siteRef = Mapping[directory];
    data.epoch = parseInt((data.TheTime - 25569) * 86400) + 6 * 3600;
    callback(null, data);
  });
};

var insert = function (db) {
  return Es.map(
    function (data, callback) {
      if (data.length) {
        var bulk = db.collection('hnet').initializeUnorderedBulkOp();
        data.forEach(function (doc) {
          bulk.insert(doc);
        });
        bulk.execute(callback);
      } else {
        callback();
      }
    }
  );
};

var batch = function (batchSize) {
  batchSize = batchSize || 1000;
  var batch = [];

  return Es.through(
    function write (data) {
      batch.push(data);
      if (batch.length === batchSize) {
        this.emit('data', batch);
        batch = [];
      }
    },
    function end () {
      if (batch.length) {
        this.emit('data', batch);
        batch = [];
      }
      this.emit('end');
    }
  );
};

我已經使用流更新了您的 tomongo.js 腳本。 我還更改了它的文件 i/o 使用異步而不是同步。

我使用小數據集根據您的代碼中定義的結構對此進行了測試,並且效果非常好。 我用 900xfiles 和 288xlines 對 3xdirs 做了一些有限的測試。 我不確定你的數據的每一行有多大,所以我加入了一些隨機屬性。它非常快。 看看它如何處理您的數據。 如果它導致問題,您可以嘗試在執行批量插入操作時使用不同的寫入問題來限制它。

還可以查看其中一些鏈接以獲取有關 node.js 中流的更多信息:

http://nodestreams.com -由 John Resig 編寫的帶有許多流示例的工具。

event-stream是一個非常有用的流模塊。

正如羅比所說,流是解決這個問題的方法。 fs.createReadStream()應使用的.readFileSync() 我首先創建一個行閱讀器,它采用路徑和您想要拆分的任何字符串/正則表達式:

linereader.js

var fs = require("fs");
var util = require("util");
var EventEmitter = require("events").EventEmitter;

function LineReader(path, splitOn) {

    var readStream = fs.createReadStream(path);
    var self = this;
    var lineNum = 0;
    var buff = ""
    var chunk;

    readStream.on("readable", function() {

        while( (chunk = readStream.read(100)) !== null) {
            buff += chunk.toString();
            var lines = buff.split(splitOn);

            for (var i = 0; i < lines.length - 1; i++) {
                self.emit("line",lines[i]);
                lineNum += 1;
            }
            buff = lines[lines.length - 1];
        }
    });
    readStream.on("close", function() {
        self.emit("line", buff);
        self.emit("close")
    });
    readStream.on("error", function(err) {
        self.emit("error", err);
    })
}
util.inherits(LineReader, EventEmitter);
module.exports = LineReader;

這將讀取一個文本文件,並為讀取的每一行發出“行”事件,因此您不會一次將所有這些事件都保存在內存中。 然后,使用 async 包(或您想要使用的任何異步循環),循環插入每個文檔的文件:

應用程序.js

var LineReader = require("./linereader.js");
var async = require("async");

var paths = ["./text1.txt", "./text2.txt", "./path1/text3.txt"];
var reader;

async.eachSeries(paths, function(path, callback) {

    reader = new LineReader(path, /\n/g);

    reader.on("line", function(line) {
        var doc = turnTextIntoObject(line);
        db.collection("mycollection").insert(doc);
    })
    reader.on("close", callback);
    reader.on("error", callback);
}, function(err) {
    // handle error and finish;
})

暫無
暫無

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

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