问题:为什么Node.js会这样执行?

我有一个 Node.js 应用程序,用于将记录从 MySql 迁移到 MongoDB。我正在使用 Mongoose 和 async.js 来执行此操作,并且我注意到一些我不理解的行为。如果我有以下 Coffeescript 代码(此处为javascript):

           # users is a collection of about 70k records
async.each users, ((user, callback) =>
    # console.log "saving user: #{user.id} of #{users[users.length-1].id}"
    model = new User
        id: user.id
        name:
            first: user.fname
            last: user.lname
    model.save (err) ->
        console.log "saving user: #{user.id}"
        model = null
        callback(err)
), (err) ->
    users = null
    callback(err)

从未达到model.save的回调,我的 Node 进程将慢慢爬升至 1.5gb。如果我检查我的 mongodb 实例,我可以看到在处理完users集合中的所有 70k 项之后,记录将开始保存到 mongodb,但它们停止在 41k 左右。

我注意到,如果我从async.each切换到async.eachSeries,则每条记录都会调用model.save并且迁移成功完成。

我假设由于某种原因,Node 在执行model.save的回调之前,针对users集合中的每个项目运行 async.each 的每次迭代,这会导致内存问题,但我不明白为什么会这样.谁能告诉我为什么 Node 会这样做,为什么切换到async.eachSeries可以解决这个问题?

解答

尼尔在提供解决方案方面做得很好,但我只是想谈谈你的问题:

谁能告诉我为什么 Node 会这样做,以及为什么切换到 async.eachSeries 可以解决这个问题?

如果您查看async.eachasync.eachSeries的详细信息,您可能会注意到async.each的文档指出:

将函数迭代器并行应用于 arr 中的每个项目

但是,async.eachSeries指出:

与 each 相同,仅迭代器应用于 arr 中的每个项目。仅在当前迭代器完成后才调用下一个迭代器。这意味着迭代器函数将按顺序完成。

详细来说,如果我们查看代码,您会发现each的代码最终调用了数组本身的原生forEach函数,并且每个元素都调用了迭代器(指向源代码的链接):

_each(arr, function (x) {
    iterator(x, only_once(done) );
});

调用:

var _each = function (arr, iterator) {
    if (arr.forEach) {
        return arr.forEach(iterator);
    }

但是,对迭代器函数的每次调用最终都会调用model.save。这个 Mongoose 函数(除其他外)最终会执行 I/O 以将数据保存到数据库中。如果您要跟踪代码路径,您会看到它最终出现在一个调用process.nextTick的函数中(链接到源代码)。

Node 的process.nextTick函数通常用于这种情况(I/O),一旦执行流程结束就会处理回调。在这种情况下,只有在 forEach 循环完成后才会调用每个回调。 (这是有目的的,并且不会阻止任何代码执行。)

所以总结一下:

使用async.each时,您上面的代码将遍历所有用户,将保存排队,但只有在代码完成对所有用户的迭代后才开始处理它们。

使用async.eachSeries时,您上面的代码将一次处理每个用户,并且仅在保存完成后处理下一个用户 - 当调用 eachSeries 回调时。

Logo

MongoDB社区为您提供最前沿的新闻资讯和知识内容

更多推荐