我如何确保某项工作在Bull中不会执行两次?


11

我有两个功能,scheduleScan()scan()

scan()scheduleScan() 在除了安排新扫描之外无其他操作时调用,因此scheduleScan()可以安排scan()。但是有一个问题,有些作业要运行两次。

我想确保在任何给定时间仅处理一项作业。我该如何实现?我相信与done()(,它已经在scan()中,现在已删除)有关,但我无法提出解决方案。

公牛版:3.12.1

重要的后期编辑: scan()调用另一个函数,它们可能会调用也可能不会调用其他函数,但是它们都是同步函数,因此它们仅在完成自己的作业时才调用一个函数,只有一种前进的方式。我在“树”的末尾调用它,最后一个函数调用scheduleScan(),但是不能同时运行两个作业。scan()顺便说一句,每个工作都始于,然后以scheduleScan(stock, period, milliseconds, 'called by file.js')

export function update(job) {
  // does some calculations, then it may call scheduleScan() or
  // it may call another function, and that could be the one calling
  // scheduleScan() function.
  // For instance, a function like finalize()
}

export function scan(job) {
  update(job)
}


import moment from 'moment'
import stringHash from 'string-hash'
const opts = { redis: { port: 6379, host: '127.0.0.1', password: mypassword' } }
let queue = new Queue('scan', opts)

queue.process(1, (job) => {
  job.progress(100).then(() => {
    scan(job)
  })
})

export function scheduleScan (stock, period, milliseconds, triggeredBy) {
  let uniqueId = stringHash(stock + ':' + period)

  queue.getJob(uniqueId).then(job => {
    if (!job) {
      if (milliseconds) {
        queue.add({ stock, period, triggeredBy }, { delay: milliseconds, jobId: uniqueId }).then(() => {
          // console.log('Added with ms: ' + stock + ' ' + period)
        }).catch(err => {
          if (err) {
            console.log('Can not add because it exists ' + new Date())
          }
        })
      } else {
        queue.add({ stock, period, triggeredBy }, { jobId: uniqueId }).then(() => {
          // console.log('Added without ms: ' + stock + ' ' + period)
        }).catch(err => {
          if (err) {
            console.log('Can not add because it exists ' + new Date())
          }
        })
      }
    } else {
      job.getState().then(state => {
        if (state === 'completed') {
          job.remove().then(() => {
            if (milliseconds) {
              queue.add({ stock, period, triggeredBy }, { delay: milliseconds, jobId: uniqueId }).then(() => {
                // console.log('Added with ms: ' + stock + ' ' + period)
              }).catch(err => {
                if (err) {
                  console.log('Can not add because it exists ' + new Date())
                }
              })
            } else {
              queue.add({ stock, period, triggeredBy }, { jobId: uniqueId }).then(() => {
                // console.log('Added without ms: ' + stock + ' ' + period)
              }).catch(err => {
                if (err) {
                  console.log('Can not add because it exists ' + new Date())
                }
              })
            }
          }).catch(err => {
            if (err) {
              // console.log(err)
            }
          })
        }
      }).catch(err => {
        // console.log(err)
      })
    }
  })
}

我找不到scan功能,可以帮忙吗?
穆罕默德Zeeshan

@MuhammadZeeshan我加了,我的错。
salep

Answers:


6

我相信问题是您的scan函数是异步的。因此,您的job.progress函数先调用scan,然后立即调用,以done允许队列处理另一个作业。

一种解决方案是将done回调作为参数传递给您的scanscheduleScan函数,并在您完成工作(或出现错误)后调用它。

另一个(更好)的解决方案可能是确保您始终Promise从发回scanand scheduleScan,然后等待诺言解决,然后再致电done。如果这样做,请确保将所有的promise返回值链接到scheduleScan函数中。

queue.process(1, (job, done) => {
  job.progress(100).then(() => {
    scan(job)
        .then(done)
        .catch(done)
  })
})

export function scan() {
   // business logic
   return scheduleScan()
}

// Chain all of your promise returns. Otherwise
// the scan function will return sooner and allow done to be called
// prior to the scheduleScan function finishing it's execution
export function scheduleScan() {
    return queue.getJob(..).then(() => {
        ....
        return queue.add()...
        ....
        return queue.add(...)
            .catch(e => {
                 console.log(e);
                 // propogate errors!
                 throw e;
             })

}

我已经编辑了我的问题,能否请您再次检查一下,特别是“重要的后期编辑”部分?您的答案在这种情况下是否仍然适用?谢谢。
salep

1
是的,它仍然有效。从您的编辑中,我想您是说scheduledScan总是在中的所有其他同步功能之后被调用scan。如果是这样,那么是的,我的答案仍然有效。只是总是返回将scheduleScanscan函数中返回的承诺
jeeves

再次,我的错误。第一个函数update()处于扫描状态,但是update()可以调用另一个函数,如finalize(),finalize()可以调用scheduleScan()。请记住,这些操作是按顺序进行的,因此不会有多个调用,我这样做是为了保持我的应用程序模块化。-谢谢
salep

1
是的,同样的答案。如果update调用scheduledScan或它们之间有任何数量的功能。关键是您需要将诺言链从头到尾scheduleScan都返回给scan函数。因此,如果scan调用update哪个调用finalise.....哪个调用scheduleScanpromise链将需要通过所有函数调用返回,即只需确保您从每个函数中返回promise。
jeeves

因此,仅是澄清我的最后评论。例如,如果进行内部扫描,则调用更新。您需要从扫描功能返回更新结果(承诺)。
jeeves

4

扫描功能是异步功能。在您的queue.process()函数中,您必须等待扫描函数,然后调用done()回调。

export async function scan(job) {
  // it does some calculations, then it creates a new schedule.
  return scheduleScan(stock, period, milliseconds, "scan.js");
}

queue.process(1, (job, done) => {
  job.progress(100).then(async() => {
    await scan(job);
    done();
  });
});

export async function scheduleScan(stock, period, milliseconds, triggeredBy) {
    let uniqueId = stringHash(stock + ":" + period);
    try {
      const existingJob = await queue.getJob(uniqueId);
      if (!existingJob) {
        const job = await addJob({
          queue,
          stock,
          period,
          uniqueId,
          milliseconds,
          triggeredBy
        });
        return job;
      } else {
        const jobState = await existingJob.getState();
        if (jobState === "completed") {
          await existingJob.remove();
          const newJob = await addJob({
            queue,
            stock,
            period,
            uniqueId,
            milliseconds,
            triggeredBy
          });
          return newJob;
        }
      }
    } catch (err) {
      throw new Error(err);
    }
}

export function addJob({ queue, stock, period, milliseconds, triggeredBy }) {
  if (milliseconds) {
    return queue.add(
      { stock, period, triggeredBy },
      { delay: milliseconds, jobId: uniqueId }
    );
  } else {
    return queue.add({ stock, period, triggeredBy }, { jobId: uniqueId });
  }
}

尝试这个!我试图通过使用async-await来重构代码。


我已经编辑了我的问题,能否请您再次检查一下,特别是“重要的后期编辑”部分?您的答案在这种情况下是否仍然适用?谢谢。
salep
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.