Одной из распространенных проблем с cron является то, что задание cron выполняется дольше, чем интервал между двумя последовательными запусками. В этом случае второй экземпляр задания запустится до завершения первого, что приведет к перекрытию и потенциально неожиданному поведению, например дублированию.

Сценарий состоит из задания, которое запускается каждые 5 секунд с использованием библиотеки CronJob, и функции delay, которая имитирует длительную задачу.

Сценарий использует простой флаг isJobRunning для предотвращения перекрывающихся запусков задания и переменную jobSkippedCount для отслеживания того, сколько раз задание было пропущено из-за перекрытия.

Мы собираемся использовать nanoid, чтобы узнать идентификаторы заданий, и cron, чтобы запланировать задания.

npm install nanoid
npm install cron

Давайте углубимся в код:

import { CronJob } from 'cron'
import { nanoid } from 'nanoid'let isJobRunning = false

let jobSkippedCount = 0

const delay = (ms) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms)
  })
}

const job = new CronJob('*/5 * * * * *', async () => {
  if (isJobRunning) {
    jobSkippedCount++
    console.log("__ JOB SKIPPED COUNT __ ", jobSkippedCount)
    if (jobSkippedCount > 2) {
         /* If a job takes more than 15 seconds 
         to complete we can stop this job, 
         throw a error and start a new job */
         console.log("__ TIMEOUT __")
         jobSkippedCount = 0
        isJobRunning = false
     }
    return
  }
  isJobRunning = true
  try {
    const id = nanoid(10)
    console.log("\n\n__ JOB RUNNING __ ", id, new Date())
      /* Your  functionality goes here 
    I have used a delay function to 
    simulate a long running job */
    await delay(16000)
    isJobRunning = false
    jobSkippedCount = 0
    console.log("__ JOB COMPLETED __ ", id, new Date())
  }
  catch (err) {
    console.error(err);
  }
})

job.start();

Есть 3 сценария, которые происходят с этим: наш цикл составляет 5 секунд один раз, а максимальное количество пропусков равно 2.

1. Все гладко

delay(2000) — Задание cron выполняется и завершает свою задачу перед следующим циклом, создавая следующий вывод:

__ JOB RUNNING   __  EC6MqpB4FG 2023-04-06T17:53:35.004Z
__ JOB COMPLETED __  EC6MqpB4FG 2023-04-06T17:53:37.017Z
__ JOB RUNNING   __  6nutPdea6_ 2023-04-06T17:53:40.004Z
__ JOB COMPLETED __  6nutPdea6_ 2023-04-06T17:53:42.005Z

2. Пропустил задание, но нет ТАЙМ-АУТА

delay(7000) — Задание cron выполняется и завершает свою задачу, пропуская один цикл, выдавая следующий результат:

__ JOB RUNNING   __  soR8V3YrQp 2023-04-06T17:54:15.006Z
__ JOB SKIPPED COUNT __  1
__ JOB COMPLETED __  soR8V3YrQp 2023-04-06T17:54:22.019Z
__ JOB RUNNING   __  8-LBtb85Jq 2023-04-06T17:54:25.002Z
__ JOB SKIPPED COUNT __  1
__ JOB COMPLETED __  8-LBtb85Jq 2023-04-06T17:54:32.004Z

3. Достигается тайм-аут

delay(17000) — Задание cron не завершается в течение тайм-аута, поэтому запускается новое задание: независимо от того, завершено предыдущее задание или нет. Мы начнем работу после тайм-аута.

Возможно, вам придется убить свою логику, используя эмиттеры событий, чтобы остановить выполнение функции.

__ JOB RUNNING   __  lrMOLDgtzm 2023-04-06T18:00:05.004Z
__ JOB SKIPPED COUNT __  1
__ JOB SKIPPED COUNT __  2
__ JOB SKIPPED COUNT __  3
__ TIMEOUT __
__ JOB COMPLETED __  lrMOLDgtzm 2023-04-06T18:00:22.015Z
__ JOB RUNNING   __  qPK1lQ8QuV 2023-04-06T18:00:25.003Z
__ JOB SKIPPED COUNT __  1
__ JOB SKIPPED COUNT __  2
__ JOB SKIPPED COUNT __  3
__ TIMEOUT __
__ JOB COMPLETED __  qPK1lQ8QuV 2023-04-06T18:00:42.003Z

isJobRunning — это флаг, указывающий, выполняется ли задание в данный момент.

jobSkippedCount – это счетчик, который отслеживает, сколько раз задание было пропущено из-за наложения. Это также помогает **TIMEOUT**. Мы можем установить максимально допустимое количество пропусков, например 5. Затем, если задание будет пропущено в 5-й раз, оно будет сброшено и начнется новое задание.

delay() — функция, которая возвращает обещание, которое разрешается через заданное количество миллисекунд. Обычно это ваша логика.

Это больше похоже на временный хак, чтобы избежать наложений, такие сценарии тайм-аута настолько опасны.

Более масштабируемый подход заключается в использовании системы очередей для управления заданиями. Это позволяет ставить в очередь несколько экземпляров задания и выполнять их последовательно, без перекрытий или дубликатов. Популярные системы очередей для Node.js включают Bull, Bee-Queue и Agenda.

Спасибо за прочтение
🕊 Peace