繁体   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