我有一个 cron 作业设置,每 5 分钟运行一次任务。但有时该任务需要超过 5 分钟才能运行,因此该任务的另一个副本由 cron 同时运行。有没有办法在任何时候或 cron 到我们可以让它在运行另一个副本之前等待另一个作业完成?
5 回答
AFAIK 你不能使用任何时候这样做,但你可以在你的脚本中处理它。这可以通过以下解决方案之一来完成
使用在作业开始时设置并在作业结束时清除的标志(或一些信息,如开始时间、结束时间、成功状态)在数据库中处理此问题,并在每次作业开始时检查此标志以查看前一个作业是否已完成或不; 但请确保在清除标志之前处理异常,就好像进程死亡一样,其他进程将无法运行
您可以通过创建一个临时文件并为当前进程对其进行排他锁,使操作系统为您工作,因此在当前进程完成之前,其他进程不能对该文件拥有排他锁,然后当进程完成它会释放锁并让其他进程工作。这样做包括这是你的 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 作业更适合您。
这类问题没有完美的答案。很大程度上取决于您的脚本在做什么,以及您系统的整体架构。您的里程会有所不同。
这是我用于 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
我认为最好的选择是任何类型的锁(使用文件、数据库等),但是当你使用锁时,你需要非常巧妙地在你的进程中实现错误处理,否则如果你的锁没有被释放,那么你的 cron 将永远不会运行再次进行该过程。
用法 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 '红宝石代码',锁:'lock_name'
job_type :runner_with_lock, "cd :path && flock -n /var/lock/:lock.lock script/rails runner -e :environment ':task' :output"