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

Node.js 如何标记多个MongoDB文档进行处理?,node.js,mongodb,Node.js,Mongodb,我正在实现一个用Node编写的web爬虫,并使用MongoDB作为应用程序的后端来存储页面及其状态。爬虫程序应该能够在多台机器上运行,除此之外,每台机器将有多个并行运行的工作程序,以加快挂起页面的爬网过程 每位工人将: 在数据库中查询仍有待爬网的部分页面 将其状态从“待定”更新为“正在进行” 爬行 将其状态从“进行中”更新为“完成” 考虑到这一点,我正试图找到让多个工作人员不同时查询相同页面的方法 每个工作人员都有其唯一的ID,因此页面只是具有如下结构的文档: { uri, status, wo

我正在实现一个用Node编写的web爬虫,并使用MongoDB作为应用程序的后端来存储页面及其状态。爬虫程序应该能够在多台机器上运行,除此之外,每台机器将有多个并行运行的工作程序,以加快挂起页面的爬网过程

每位工人将:

  • 在数据库中查询仍有待爬网的部分页面
  • 将其状态从“待定”更新为“正在进行”
  • 爬行
  • 将其状态从“进行中”更新为“完成”
  • 考虑到这一点,我正试图找到让多个工作人员同时查询相同页面的方法

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

    { uri, status, workerId, <other data> }
    
    {uri,status,workerId,}
    
    我的计划是用当前工作人员id标记
    N
    文档(通知它们将由该工作人员处理),然后查询它们

    对于具有以下状态的文档,例如
    将workerId设置为
    {“status”:“Pending”,“workerId”:null}

    然后查询具有以下内容的文档:
    {“status”:“Pending”,“workerId”:“}

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


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

    好吧,我想我理解这个目标-您希望更新所有处于挂起状态的文档并为其分配工作人员。你想把工人分配得稍微均匀一些。工作分配完成后,每个工作人员将识别要扫描的页面。但是,您不喜欢一次将光标移动到一个文档中,而是希望一次更新一组数据

    下面是一个在updateMany()函数中使用$where条件的示例。请记住$where不能使用索引。如果您在“状态”上建立索引,您可能还可以,但从性能角度来看,这可能不起作用。我相信您希望更新所有挂起的记录,因此与一次更新一条记录相比,这种方式对性能的影响可能更好。此外,我的查询谓词不考虑WorkID是否为NULL。这是因为我相信永远不应该存在状态为“待定”且workerId不为null的情况

    假设有两个worker,我的想法实现了两个update语句,一个用于worker0,另一个用于worker1。我假设您的文档有一个名为_id的字段,它是ObjectId。策略是使用_id字段时间戳。看看时间戳的秒数。对于秒数值介于0和30之间的用户,将其分配给worker0,其他所有用户将其分配给worker1。如果您有更多的员工,则需要更改此策略以适应所需员工的数量

    工人0分配:

    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} })
    
    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} })
    
    db.pages.find({status: "In Progress", workerId: 0})
    
    一旦运行这些查询,分配就完成了。现在,每个工作人员都可以通过发出各自的查询来识别要爬网的页面。例如:

    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} })
    
    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} })
    
    db.pages.find({status: "In Progress", workerId: 0})
    
    工人0已完成:

    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} })
    
    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} })
    
    db.pages.find({status: "In Progress", workerId: 0})
    
    一旦工作程序对页面进行爬网,它可以标记记录已完成,以防止将来的多次爬网运行

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

    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} })
    
    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} })
    
    db.pages.find({status: "In Progress", workerId: 0})
    
    我很好奇你对这种方法的想法,并感谢任何反馈,无论是好是坏。点火

    思考之后


    一种完全不同的方法可能是,在最初使用随机分配插入记录时分配工作者。但是,这对已经使用空分配创建的记录没有帮助。

    没有创建单独的调度程序进程来分配工作,可能是三阶段方法

  • 查询具有限制的挂起文档,仅检索_id字段。如果您在{status:1,workerId:1,_id:1}上有一个索引,那么这可能会影响性能
  • 使用$in运算符进行更新,将状态设置为“进行中”,并分配工作人员ID
  • 查询正在进行和工作人员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})  
    

    如果您有多个员工同时进入,则可能会发生竞争,他们可能都试图获得相同的页面。您可以在a中执行上述步骤以避免出现这种情况。

    您运行的是哪个版本的MongoDB,您是否进行了切分?这不只是一个关于使用查询更新多个文档的问题吗@RobertMoskal-是的,但我认为更具体地说,如何构造update语句的find部分,以便选择记录是伪随机的,以便均匀分布。