如何标记多个MongoDB文档进行处理?

时间:2019-10-04 13:39:32

标签: node.js mongodb

我正在实现用Node编写的Web爬网程序,并将MongoDB用作我的应用程序的后端来存储页面及其状态。抓取工具应该能够在多台计算机上运行,​​此外,每台计算机还将有多个并行运行的工作程序,以加快待处理页面的抓取过程。

每个工人将:

  1. 在数据库中查询一些尚待抓取的页面
  2. 将其状态从“待处理”更新为“进行中”
  3. 抓取它们
  4. 将其状态从“进行中”更新为“完成”

考虑到这一点,我正在尝试寻找多个工作人员同时同时查询同一页面的方法。

每个工作人员都有其唯一的ID,因此页面只是具有以下结构的文档:

{ uri, status, workerId, <other data> }

我的计划是将N个文档标记为当前工作人员ID(通知他们将由该工作人员处理),然后查询它们

对于set workerId to <currentWorkerId>

的文档,类似{ "status": "Pending", "workerId": null }

然后查询具有以下内容的文档:{ "status": "Pending", "workerId": "<currentWorkerId>" }

问题是,据我所知mongo不支持有限制的更新。当然,我可以执行N更新操作来更新单个文档,但是我想知道对于这种任务是否有更惯用/优雅的解决方案?

最后,我的目标是确保每当2个或更多工作人员查询要处理的页面时,他们都不会两次检索同一页面。

2 个答案:

答案 0 :(得分:0)

好吧,我想我了解目标-您希望更新所有处于待处理状态的文档,并为其分配一个工作器。您想将工人均匀地分配。完成工作人员分配后,每个工作人员将识别其要扫描的页面。但是您不喜欢一次在一个文档上移动光标的想法,而是希望一次更新一组数据。

这里是在updateMany()函数中使用$ where条件的示例。请记住,$ where不能使用索引。如果您以“状态”为索引,则可能会好的,但是从性能的角度来看这可能行不通。我认为您希望更新所有未决记录,因此与一次更新一个记录相比,这种方式对性能的影响可能会更好。另外,我的查询谓词不考虑workerId是否为null。这是因为我相信永远都不会出现状态为“待处理”且workerId不为空的情况。

假设有两个工作程序,我的想法实现了两个更新语句,一个是针对worker0,另一个是针对worker1。我假设您的文档有一个名为_id的字段,它是一个ObjectId。该策略是使用_id字段时间戳。查看时间戳的秒数。对于秒值为0到30的秒分配给worker0,所有其他秒分配给worker1。如果您有更多的工人,则需要更改此策略以适应所需工人的数量。

worker0分配:

db.pages.updateMany({"status": "Pending", $where: function(){
        var seconds = this._id.getTimestamp().getSeconds()
        if(seconds >= 0 && seconds < 30) {
            return true;
        }
        else {
            return false;
        }
    }
}, { $set: { status: "In Progress", workerId: 0} })

worker1分配:

db.pages.updateMany({"status": "Pending", $where: function(){
        var seconds = this._id.getTimestamp().getSeconds()
        if(seconds >= 30) {
            return true;
        }
        else {
            return false;
        }
    }
}, { $set: { status: "In Progress", workerId: 1} })

一旦运行这些查询,分配即告完成。现在,每个工作人员都可以通过发出自己的查询来识别要爬网的页面。例如:

Worker0标识要爬网的页面:

db.pages.find({status: "In Progress", workerId: 0})

Worker0完成:

一旦工作人员对页面进行爬网,它就可以将记录标记为已完成,以防止将来再次进行爬网。

db.pages.updateOne({_id: ObjectId("5db0b1953cf0c979dd020fa2")}, { $set: {status: "Finished"}})

结论:

我很好奇您对这种方法的想法,并感谢您提供的任何反馈意见(无论好坏)。燃烧着!

思考后

当最初使用随机分配插入记录时,可以分配完全不同的方法。但是,这无助于已经使用空分配创建的记录。

答案 1 :(得分:0)

无需创建单独的调度程序来分配工作,可能是三阶段方法。

  1. 查询未决文档,其限制为仅检索_id字段。如果您在{status:1,workerId:1,_id:1}上拥有索引,则可以提高性能
  2. 使用$ in运算符进行更新以将状态设置为“进行中”并分配工作人员ID
  3. 查询进行中和工作人员ID

类似的东西:

var ids = db.pages.find({status:"pending", workerId: null},{_id:1}).limit(100).toArray().map(p=>p._id)

db.pages.updateMany({_id:{$in:ids}},{$set:{status:"In Progress", worker: MyID}})

var workcursor = db.pages.find({status:"In Progress", worker: MyID})  

如果您有多个工人同时进来,则有可能发生一场比赛,他们俩都可能试图获得相同的页面。您可以在transaction中执行上述步骤来避免这种情况。