Javascript Node.js:可控并发while循环

Javascript Node.js:可控并发while循环,javascript,node.js,concurrency,Javascript,Node.js,Concurrency,我收集了170万张mongodb唱片。每个记录都是一个ID号。我需要读取每个ID号,对另一个服务执行一些请求,转换数据,将其写入不同的集合,如果所有操作都成功,则删除原始ID记录 我想要一个脚本,该脚本可以无限期地执行这些操作,直到集合为空,并具有可指定的并发性(即任何时候最多3个请求) 通常我会使用Bluebird的映射,它可以指定并发承诺的数量,但没有输入数组(除非我将所有输入记录读取到内存中,我不会这样做) 我想要的本质上是一个并发while循环,即:(伪javascript) 您可以使用

我收集了170万张mongodb唱片。每个记录都是一个ID号。我需要读取每个ID号,对另一个服务执行一些请求,转换数据,将其写入不同的集合,如果所有操作都成功,则删除原始ID记录

我想要一个脚本,该脚本可以无限期地执行这些操作,直到集合为空,并具有可指定的并发性(即任何时候最多3个请求)

通常我会使用Bluebird的
映射
,它可以指定并发承诺的数量,但没有输入数组(除非我将所有输入记录读取到内存中,我不会这样做)

我想要的本质上是一个并发while循环,即:(伪javascript)


您可以使用mongodb的游标异步迭代所有记录。要让三名工作人员处理该任务,请将该任务包装成一个异步函数,并多次调用该函数:

 const cursor = db.collection("records").find({});

 async function process() {
   while(await cursor.hasNext()) {
     const record = await cursor.next();
     //...
   }
 }

 await Promise.all([ process(), process(), process() ]);
(我不确定mongodb驱动程序是否支持对
.next()
的并发调用,但是,您应该对此进行测试)


否则,此信号量实现可能会有所帮助:

 function Semaphore(count = 1) {
  const resolvers = [];
  let startCount = count;

   return {
     aquire() {
       return new Promise(resolve => {
         if(startCount) { resolve(); startCount -= 1; }
         else resolvers.push(resolve);
       });
     },
     free() { 
       if(resolvers.length) resolvers.pop()(); 
       else startCount += 1;
     },
     async use(cb) { 
       await this.aquire(); 
       await cb(); 
       this.free() 
     },
     async done() {
       await Promise.all(Array.from({ length: count }, () => this.aquire()));
       startCount = count;
     },
   };
 }
在您的情况下,它可用作:

 const connectionSemaphore = Semaphore(3);

 (async fuction() {
    while(await cursor.hasNext()) {
      const record = await cursor.next();
      /*await*/ connectionSemaphore.use(async () => {
        // Do connection stuff concurrently
      });
    }

    await connectionSemaphore.done();
 })();

您可以使用mongodb的游标异步迭代所有记录。要让三名工作人员处理该任务,请将该任务包装成一个异步函数,并多次调用该函数:

 const cursor = db.collection("records").find({});

 async function process() {
   while(await cursor.hasNext()) {
     const record = await cursor.next();
     //...
   }
 }

 await Promise.all([ process(), process(), process() ]);
(我不确定mongodb驱动程序是否支持对
.next()
的并发调用,但是,您应该对此进行测试)


否则,此信号量实现可能会有所帮助:

 function Semaphore(count = 1) {
  const resolvers = [];
  let startCount = count;

   return {
     aquire() {
       return new Promise(resolve => {
         if(startCount) { resolve(); startCount -= 1; }
         else resolvers.push(resolve);
       });
     },
     free() { 
       if(resolvers.length) resolvers.pop()(); 
       else startCount += 1;
     },
     async use(cb) { 
       await this.aquire(); 
       await cb(); 
       this.free() 
     },
     async done() {
       await Promise.all(Array.from({ length: count }, () => this.aquire()));
       startCount = count;
     },
   };
 }
在您的情况下,它可用作:

 const connectionSemaphore = Semaphore(3);

 (async fuction() {
    while(await cursor.hasNext()) {
      const record = await cursor.next();
      /*await*/ connectionSemaphore.use(async () => {
        // Do connection stuff concurrently
      });
    }

    await connectionSemaphore.done();
 })();

最近我不得不做两次这样的事情。我的问题不涉及那么多的记录,我需要将结果合并到一个数据结构中,但我认为这可能是您所寻找的一个开始

概括这些解决方案会给我带来如下结果:

//processQueue::((数字->承诺[a]),数字,(a->b),(c[b])->c)->承诺c
const processQueue=(队列、计数、进程、组合、初始化)=>
队列(计数)
.then(items=>items.map(流程))
.然后(承诺=>Promise.all(承诺))
.然后(当前=>当前长度
?处理队列(队列、计数、处理、合并、合并(初始、当前))
:联合收割机(初始、当前)
)
这需要五个参数:

  • queue
    是一个函数,它接受一个数字并返回一个值列表的承诺
  • count
    是一个数字
  • process
    是一个将这些值之一转换为另一种类型的函数
  • combine
    是一个将目标类型和第二种类型的列表组合到目标类型中的函数
  • init
    是减速器的起始值
它返回该目标类型的值的承诺

我无法用您的基础架构进行演示,但构建一个简单的示例并不难。首先,我们可以编写一个伪
queue
函数,该函数返回最多为
n
项的组的承诺,直到没有更多项为止,然后返回一个空列表的承诺。下面是一个愚蠢的版本:

const queue=((v)=>(count)=>Promise.resolve(
from({length:Math.min(count,10-v+1)},()=>({id:v++}))
)) (1)
队列(3)。然后(console.log)/~>[{id:1},{id:2},{id:3}]
队列(3).then(console.log)/~>[{id:4},{id:5},{id:6}]
队列(3).then(console.log)/~>[{id:7},{id:8},{id:9}]
队列(3)。然后(console.log)/~>[{id:10}]

队列(3)。然后(console.log)/~>[]//(并将永远返回空列表)
最近我不得不做两次这样的事情。我的问题不涉及那么多的记录,我需要将结果合并到一个数据结构中,但我认为这可能是您所寻找的一个开始

概括这些解决方案会给我带来如下结果:

//processQueue::((数字->承诺[a]),数字,(a->b),(c[b])->c)->承诺c
const processQueue=(队列、计数、进程、组合、初始化)=>
队列(计数)
.then(items=>items.map(流程))
.然后(承诺=>Promise.all(承诺))
.然后(当前=>当前长度
?处理队列(队列、计数、处理、合并、合并(初始、当前))
:联合收割机(初始、当前)
)
这需要五个参数:

  • queue
    是一个函数,它接受一个数字并返回一个值列表的承诺
  • count
    是一个数字
  • process
    是一个将这些值之一转换为另一种类型的函数
  • combine
    是一个将目标类型和第二种类型的列表组合到目标类型中的函数
  • init
    是减速器的起始值
它返回该目标类型的值的承诺

我无法用您的基础架构进行演示,但构建一个简单的示例并不难。首先,我们可以编写一个伪
queue
函数,该函数返回最多为
n
项的组的承诺,直到没有更多项为止,然后返回一个空列表的承诺。下面是一个愚蠢的版本:

const queue=((v)=>(count)=>Promise.resolve(
from({length:Math.min(count,10-v+1)},()=>({id:v++}))
)) (1)
队列(3)。然后(console.log)/~>[{id:1},{id:2},{id:3}]
队列(3).then(console.log)/~>[{id:4},{id:5},{id:6}]
队列(3).then(console.log)/~>[{id:7},{id:8},{id:9}]
队列(3)。然后(console.log)/~>[{id:10}]

队列(3)。然后(console.log)/~>[]//(并将永远返回空列表)
您应该能够使用
var stream=collection.find().batchSize(3).stream()函数查看所有记录。然后,您可以在数据事件
stream.on('data'.fun…)
中读取一批3条记录,您可以在