我正在实现用Node编写的Web爬网程序,并将MongoDB用作我的应用程序的后端来存储页面及其状态。抓取工具应该能够在多台计算机上运行,此外,每台计算机还将有多个并行运行的工作程序,以加快待处理页面的抓取过程。
每个工人将:
考虑到这一点,我正在尝试寻找多个工作人员同时不同时查询同一页面的方法。
每个工作人员都有其唯一的ID,因此页面只是具有以下结构的文档:
{ uri, status, workerId, <other data> }
我的计划是将N
个文档标记为当前工作人员ID(通知他们将由该工作人员处理),然后查询它们
对于set workerId to <currentWorkerId>
{ "status": "Pending", "workerId": null }
然后查询具有以下内容的文档:{ "status": "Pending", "workerId": "<currentWorkerId>" }
问题是,据我所知mongo不支持有限制的更新。当然,我可以执行N
更新操作来更新单个文档,但是我想知道对于这种任务是否有更惯用/优雅的解决方案?
最后,我的目标是确保每当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)
无需创建单独的调度程序来分配工作,可能是三阶段方法。
类似的东西:
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中执行上述步骤来避免这种情况。