【问题标题】:How can I prevent overlapping cron jobs with Rails?如何防止与 Rails 重叠的 cron 作业?
【发布时间】:2012-10-19 08:35:06
【问题描述】:

我有一个 cron 作业设置,每 5 分钟运行一次任务。但有时该任务需要超过 5 分钟才能运行,因此该任务的另一个副本由 cron 同时运行。有没有办法在任何时候或 cron 到我们可以让它等待另一个作业完成,然后再运行另一个副本?

【问题讨论】:

  • 我会说你会在 cron 执行的脚本中而不是在 cron 本身中执行此操作,但如果 cron 可以执行此操作,它可能是一个简单的解决方案(尽管边界情况太多,我想你会想要等待,除非已经有另一个任务在等待,在这种情况下最好跳过一轮)

标签: ruby-on-rails crontab race-condition whenever


【解决方案1】:

AFAIK 您不能使用任何时候执行此操作,但您可以在脚本中处理它。 这可以通过以下解决方案之一来完成

  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
    

第二种方法的优点是即使进程意外终止,锁也会被操作系统自动释放

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

更多详情请查看this link

【讨论】:

    【解决方案2】:

    使用文件系统或数据库锁

    您不能使用 cron 或类似方法来防止重叠——至少不能直接使用——但您有很多选择。您可以在生成新任务之前检查正在运行的任务的进程列表,但这仍然容易受到竞争条件的影响。一些更好的选择是:

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

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

    【讨论】:

      【解决方案3】:

      这是我的变体,带有用于 rails rake 任务的文件锁定。

      把它放在你的 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
      

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          使用 script_with_lock 'script_name', lock: '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: 'lock_name'

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

          【讨论】:

            猜你喜欢
            • 2016-04-22
            • 2014-11-25
            • 2015-04-19
            • 2017-05-12
            • 1970-01-01
            • 1970-01-01
            • 2016-07-23
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多