Как я могу предотвратить перекрытие заданий cron с Rails?

У меня есть настройка задания cron для запуска задачи каждые 5 минут. Но иногда выполнение задачи занимает> 5 минут, поэтому cron одновременно запускает другую копию этой задачи. Есть ли способ, когда или cron, чтобы мы могли дождаться завершения другого задания, прежде чем запускать другую копию?


person Pratik Khadloya    schedule 30.10.2012    source источник
comment
Я бы сказал, что вы бы сделали это в сценарии, который выполняет cron, а не в самом cron, но если cron может это сделать, это может быть простым решением (хотя есть слишком много пограничных случаев, я полагаю, вы хотели бы подождать , если там уже не ждет другое задание, и в этом случае лучше пропустить раунд)   -  person Jasper    schedule 30.10.2012


Ответы (5)


Насколько я знаю, вы не можете сделать это, используя всякий раз, но вы можете справиться с этим в своем сценарии. Это можно сделать одним из следующих решений

  1. обработка этого в базе данных с использованием флага (или некоторой информации, такой как время начала, время окончания, статус успеха), который устанавливается при запуске задания и очищается при завершении задания, и проверяйте этот флаг каждый раз, когда задание начинается, чтобы увидеть, выполнено ли предыдущее задание или нет; но обязательно обрабатывайте исключения, как если бы процесс умирал перед очисткой флага, никакой другой процесс не сможет работать

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

    file = File.new("cron.lock", "a")
    can_lock = file.flock(File::LOCK_EX | File::LOCK_NB)
    
    if can_lock == false
      exit 1
    else
      #do whatever you want
    end
    

Преимущество второго метода заключается в том, что даже если процесс неожиданно завершится, ОС автоматически снимет блокировку.

Для меня я выбрал первый метод, так как мне нужно было запустить другой процесс, если предыдущий процесс завершился или занял больше времени, чем определенный срок.

для получения более подробной информации перейдите по этой ссылке

person Moustafa Samir    schedule 19.02.2013

Использовать блокировки файловой системы или базы данных

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

  1. Используйте семафоры или блокировки файлов в сценарии оболочки. flock и lockfile отлично подходят для этой цели.
  2. Если ваше задание cron связано с изменениями в базе данных, используйте таблицу с блокировкой на уровне строк или столбец семафора, чтобы предотвратить изменения во время работы другого процесса.
  3. Увеличьте интервал между заданиями cron, чтобы ваш процесс успел завершиться до следующего запуска. Даже если вы используете один из других вариантов, это, вероятно, хорошая идея.
  4. Сделайте свой сценарий идемпотентным, чтобы параллельные операции не влияли друг на друга.
  5. Посмотрите, является ли очередь или одноэлементный процесс лучшим вариантом для вас, чем задание cron.

Для такого рода вопросов нет идеального ответа. Многое зависит от того, что делает ваш скрипт, и от общей архитектуры вашей системы. Ваш пробег будет варьироваться.

person Todd A. Jacobs    schedule 19.02.2013

Вот мой вариант с файловой блокировкой для задач грабли на рельсах.

Поместите это в свой файл задачи rake (в пространстве имен, чтобы он не пересекался с другими задачами rake):

def cron_lock(name)
  path = Rails.root.join('tmp', 'cron', "#{name}.lock")
  mkdir_p path.dirname unless path.dirname.directory?
  file = path.open('w')
  return if file.flock(File::LOCK_EX | File::LOCK_NB) == false
  yield
end

Применение:

cron_lock 'namespace_task_name' do
  # your code
end

полный пример:

namespace :service do
  def cron_lock(name)
    path = Rails.root.join('tmp', 'cron', "#{name}.lock")
    mkdir_p path.dirname unless path.dirname.directory?
    file = path.open('w')
    return if file.flock(File::LOCK_EX | File::LOCK_NB) == false
    yield
  end

  desc 'description'
  task cleaning: :environment do
    cron_lock 'service_cleaning' do
      # your code
    end
  end
end
person Fedcomp    schedule 25.08.2015

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

person vcxz    schedule 14.03.2013

использование script_with_lock 'script_name', блокировка: 'lock_name'

job_type :script_with_lock, "cd :path && :environment_variable=:environment flock -n /var/lock/:lock.lock bundle exec script/:task :output"

использование runner_with_lock 'ruby code', замок: 'lock_name'

job_type :runner_with_lock, "cd :path && flock -n /var/lock/:lock.lock script/rails runner -e :environment ':task' :output"
person yunixon    schedule 29.10.2019