Ruby on rails 如何防止cron作业与Rails重叠?

Ruby on rails 如何防止cron作业与Rails重叠?,ruby-on-rails,crontab,race-condition,whenever,Ruby On Rails,Crontab,Race Condition,Whenever,我有一个cron作业设置,每5分钟运行一次任务。但有时任务的运行时间超过5分钟,因此cron会同时运行该任务的另一个副本。在where或cron to中是否有一种方法可以让它在运行另一个副本之前等待另一个作业完成?好的,您不能使用where本身执行此操作,但您可以在脚本中处理此操作。 这可以通过以下解决方案之一实现 在数据库中使用一个标志(或一些信息,如开始时间、结束时间、成功状态)来处理此问题,该标志在作业开始时设置,在作业结束时清除,并在每次作业开始时检查此标志,查看上一个作业是否完成;但请

我有一个cron作业设置,每5分钟运行一次任务。但有时任务的运行时间超过5分钟,因此cron会同时运行该任务的另一个副本。在where或cron to中是否有一种方法可以让它在运行另一个副本之前等待另一个作业完成?

好的,您不能使用where本身执行此操作,但您可以在脚本中处理此操作。 这可以通过以下解决方案之一实现

  • 在数据库中使用一个标志(或一些信息,如开始时间、结束时间、成功状态)来处理此问题,该标志在作业开始时设置,在作业结束时清除,并在每次作业开始时检查此标志,查看上一个作业是否完成;但请确保在清除标志之前处理异常,就好像进程已死亡一样,否则其他进程将无法运行

  • 您可以通过为当前进程创建临时文件并在其上具有独占锁,使操作系统作为您的标志工作,这样,在当前进程完成之前,其他进程不能在此文件上具有独占锁,然后当进程完成时,它将释放锁并让其他进程工作。要做到这一点,这是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
    
  • 第二种方法的优点是,即使进程意外终止,操作系统也会自动释放锁

    对于我来说,我选择了第一种方法,因为如果前一个过程完成或花费的时间超过特定的时间限制,我需要启动另一个过程

    有关更多详细信息,请检查使用文件系统或数据库锁 您不能使用cron或类似工具来防止重叠——至少不能直接避免——但您有很多选择。您可以在生成新任务之前检查正在运行的任务的进程列表,但这仍然容易受到竞争条件的影响。一些更好的选择是:

  • 在shell脚本中使用信号量或文件锁。flock和lockfile是用于此目的的优秀shell实用程序
  • 如果cron作业涉及对数据库的更改,请使用具有行级锁定的表或信号量列,以防止在另一个进程运行时进行更改
  • 增加cron作业之间的间隔,以便进程有时间在下次运行之前完成。即使您使用其他选项之一,这也可能是一个好主意
  • 使脚本幂等,以便并发操作不会相互影响
  • 查看队列或单例进程是否比cron作业更适合您

  • 对于这类问题没有完美的答案。这在很大程度上取决于脚本所做的工作以及系统的总体架构。您的里程会有所不同。

    我认为最好的选择是任何类型的锁(使用文件、数据库等),但是当您使用锁时,您需要非常巧妙地在流程中实现错误处理,否则如果您的锁没有释放,那么您的cron将永远不会再次运行该流程。

    下面是我为rails rake任务使用文件锁的变体

    将其放在rake任务文件中(在名称空间下,这样它就不会与其他rake任务重叠):

    用法:

    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
    

    使用带有锁定“script\u name”的脚本,锁定:“lock\u name”

    job_type :script_with_lock, "cd :path && :environment_variable=:environment flock -n /var/lock/:lock.lock bundle exec script/:task :output"
    
    job_type :runner_with_lock, "cd :path && flock -n /var/lock/:lock.lock script/rails runner -e :environment ':task' :output"
    
    使用runner_和_lock'ruby code',lock'lock\u name'

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

    我想说的是,您可以在cron执行的脚本中执行,而不是在cron本身中执行,但是如果cron能够做到这一点,这可能是一个简单的解决方案(尽管边界情况太多,我想您可能希望等待,除非已经有另一个任务在等待,在这种情况下,最好跳过一轮)