如何在Node.js MondoDB驱动程序中禁用操作队列

时间:2018-10-07 13:08:27

标签: node.js mongodb

我只是在使用以下代码写入MongoDB:

let MongoClient = require('mongodb').MongoClient
MongoClient.connect(mongoUrl,function(error,db){
    if (error) throw new Error(error);
    var dbo = db.db(mongoDatabase);
    dbo.collection(myCollection).insertOne(item, function(err, res) {
        if (err) throw err;
        db.close()
    })
})

如果我仅对一个插入执行上述代码,则一切正常。 但是,如果我要执行许多插入操作,例如,如果我在循环语句中调用上面的代码,则真正的写操作会在循环结束时一次全部发生。

例如,如果我在100个循环的循环中调用上述代码,则第100个循环之后的所有相同时间都会发生100次插入。最大的问题是,发生插入操作时,会同时打开许多与MongoDB的连接,并且如果我超过了打开的最大文件数操作系统限制,我还会遇到“打开的文件太多”错误。

我知道我可以使用bulkWrite和insertMany操作,但是我只想知道是否有一种方法可以在每个insertOne之后而不是循环结束时在数据库中进行真正的写入。

似乎MongoClient管理着某种队列,等待对同一集合进行类似的操作,在这种情况下,它会收集所有这些操作以一次完成所有操作。我只想避免这种情况,并使每个操作实时发生。


更新-2018年10月10日

如果我使用以下代码调用上面发布的代码

function sleep(milliseconds) {
    var start = new Date().getTime();
    for (var i = 0; i < 1e7; i++) {
        if ((new Date().getTime() - start) > milliseconds){
            break;
        }
    }
}

drawModel.saveDraw({test: "1"})
sleep(2000)
drawModel.saveDraw({test: "1"})
sleep(2000)
drawModel.saveDraw({test: "1"})
sleep(2000)
drawModel.saveDraw({test: "1"})
sleep(2000)
drawModel.saveDraw({test: "1"})
sleep(2000)

在执行代码期间,我没有看到数据库中的任何更改,并且在mongo控制台中,我也没有看到任何写入。我插入2秒钟的睡眠,以便有时间在执行代码时进行验证。 在最后一次睡眠结束时,所有写入操作均同时发生。

2018-10-10T15:44:25.649+0200 I NETWORK  [initandlisten] connection accepted from 127.0.0.1:54855 #5 (5 connections now open)
2018-10-10T15:44:25.649+0200 I NETWORK  [initandlisten] connection accepted from 127.0.0.1:54856 #6 (6 connections now open)
2018-10-10T15:44:25.649+0200 I NETWORK  [initandlisten] connection accepted from 127.0.0.1:54857 #7 (7 connections now open)
2018-10-10T15:44:25.649+0200 I NETWORK  [initandlisten] connection accepted from 127.0.0.1:54858 #8 (8 connections now open)
2018-10-10T15:44:25.649+0200 I NETWORK  [initandlisten] connection accepted from 127.0.0.1:54859 #9 (9 connections now open)
2018-10-10T15:44:25.658+0200 I NETWORK  [conn5] end connection 127.0.0.1:54855 (8 connections now open)
2018-10-10T15:44:25.658+0200 I NETWORK  [conn6] end connection 127.0.0.1:54856 (7 connections now open)
2018-10-10T15:44:25.658+0200 I NETWORK  [conn7] end connection 127.0.0.1:54857 (6 connections now open)
2018-10-10T15:44:25.658+0200 I NETWORK  [conn8] end connection 127.0.0.1:54858 (5 connections now open)
2018-10-10T15:44:25.659+0200 I NETWORK  [conn9] end connection 127.0.0.1:54859 (4 connections now open)

如您所见,并发写入会导致并发连接和随之而来的并发文件打开。我想避免这种情况,并使写入操作实时发生。

1 个答案:

答案 0 :(得分:0)

这通常是关于节点编程的更一般的事情。一切都在单个线程中运行,回调在循环后运行,这是使用回调API的;要逐个序列化操作,您需要将下一个操作放入上一个操作的回调中。您不能使用for或while之类的循环。您必须编写一个递归函数来序列化操作。批处理功能是为您简化事情的地方

如果将诺言样式API与较新版本的节点一起使用,您将能够使用async await语法并编写看起来像带有循环的同步代码

编辑:

当我说过无法通过循环执行此操作时,我并不是说要手动写出循环操作。我的意思是为了确保您的操作一次完成(序列化),您需要将下一个操作放入上一个操作的回调中。例如,您有三个异步执行功能的函数-使用回调API,它们采用一个回调参数,这是约定的最后一个参数。为了确保这三件事依次发生,您需要编写如下代码:

function doThings (callback) {
  firstThing(argsForFirstThing, function (error, resultOfFirstThing) {
    if (error) console.log(err)
    else secondThing(argsForSecondThing, function (error, resultOfSecondThing) {
      if (error) console.log(err)
      else thirdThing(argsForThirdThing, callback)
    }
  }
}

要依次执行不确定的事情,您将需要编写一个递归函数或查看npm的async

EDIT2:

关于您发布的代码; NodeJS是单线程事件分配器。一批代码将运行完毕,并且回调仅在此之后发生。不要那样阻塞代码,因为在您放弃控制之前,回调显然不会执行

如果您确实要等待,请使用setTimeout,如下所示:

// .. do stuff
setTimeout(function () {
  console.log('the rest of your code here after 2 seconds')
}, 2000)

在您的示例中,假设saveDraw看起来像这样:

saveDraw(options, callback) {
  MongoClient.connect(mongoUrl,function(error,db){
    if (error) throw new Error(error);
    var dbo = db.db(mongoDatabase);
    dbo.collection(myCollection).insertOne(item, function(err, res) {
        if (err) throw err;
        db.close(callback)
    })
})

请注意传递给close的回调参数。然后您的调用代码应如下所示:

saveDraw({test: '1'}, (err) => {
  saveDraw({test: '2'}, (err) => {
    saveDraw({test: '3'} (err) => {
      console.log('I saved 3 things')
    })
  })
})