防止长时间的cron作业并行运行的通用解决方案?


27

我正在寻找一种简单通用的解决方案,该解决方案将允许您在crontab中执行任何脚本或应用程序,并阻止其运行两次。

解决方案应独立于执行的命令。

我认为它应该看起来像lock && (command ; unlock)如果有另一个锁,锁将返回false。

第二部分就像它在执行命令后获取了lock,run命令和unlock一样,即使它返回错误。

Answers:


33

看一看运行安装运行一包。run-one命令联机帮助页图标的手册页中

run-one是一个包装脚本,运行的脚本最多包含一个带有一组唯一参数的命令的唯一实例。

当您一次只需要运行一个副本时,这对于cronjobs通常很有用。

time或一样sudo,您只需将其放在命令之前。因此,cronjob可能看起来像:

  */60 * * * *   run-one rsync -azP $HOME example.com:/srv/backup

有关更多信息和背景,请查看Dustin Kirkland 对其进行介绍的博客文章


5

设置锁的非常简单的方法:

if mkdir /var/lock/mylock; then
  echo "Locking succeeded" >&2
else
  echo "Lock failed - exit" >&2
  exit 1
fi

要运行的脚本需要创建锁。如果该锁存在,则另一个脚本正忙,因此第一个脚本无法运行。如果该文件不存在,则说明没有脚本获得该锁。因此,当前脚本获得了锁。脚本完成后,需要通过删除锁来重新获得锁。

有关bash锁的更多信息,请查看


1
您还需要一个EXIT陷阱来删除退出时的锁定。echo "Locking succeeded" >&2; trap 'rm -rf /var/lock/mylock' EXIT
盖尔哈2012年

1
理想情况下,您希望从运行您想要的命令作为子任务的进程中使用咨询群集。这样,如果所有人都死了,羊群会自动释放,而使用存在锁定文件则不会释放羊群。使用网络端口将以类似的方式工作-尽管这是较小的名称空间的方式,这是一个问题。
Alex North-Keys

3

无需安装一些精美的软件包:

#!/bin/bash
pgrep -xf "$*" > /dev/null || "$@"

自己编写该脚本比运行“ apt-get install”更快,不是吗?您可能要在pgrep中添加“ -u $ {id -u)”以检查仅由当前用户运行的实例。


2
这不能保证单个实例。两个脚本可以同时传递给||操作员的另一端,而这两个脚本都有机会启动该脚本。
塞达特·卡帕诺格鲁

@SedatKapanoglu当然,该脚本不是防竞争条件的,但最初的问题是关于长时间运行的cron-jobs(最多每分钟运行一次)。如果您的系统需要一分钟以上的时间来创建流程,那么您还有其他问题。但是,如果出于某些其他原因需要,则可以使用flock(1)保护上述脚本以防竞争。
Michael Kowhan '16

我用了这个,但是对于一个bash脚本,它应该检查一下自己。代码是这样的:v = $(pgrep -xf“ / bin / bash $ 0 $ @”)[“ $ {v / $ BASHPID /}”!=“”] &&出口2
ahofmann

3

另请参见Tim Kay的solo,它通过在用户唯一的回送地址上绑定端口来执行锁定:

http://timkay.com/solo/

如果他的网站出现故障:

用法:

solo -port=PORT COMMAND

where
    PORT        some arbitrary port number to be used for locking
    COMMAND     shell command to run

options
    -verbose    be verbose
    -silent     be silent

像这样使用它:

* * * * * solo -port=3801 ./job.pl blah blah

脚本:

#!/usr/bin/perl -s
#
# solo v1.7
# Prevents multiple cron instances from running simultaneously.
#
# Copyright 2007-2016 Timothy Kay
# http://timkay.com/solo/
#
# It is free software; you can redistribute it and/or modify it under the terms of either:
#
# a) the GNU General Public License as published by the Free Software Foundation;
#    either version 1 (http://dev.perl.org/licenses/gpl1.html), or (at your option)
#    any later version (http://www.fsf.org/licenses/licenses.html#GNUGPL), or
#
# b) the "Artistic License" (http://dev.perl.org/licenses/artistic.html), or
#
# c) the MIT License (http://opensource.org/licenses/MIT)
#

use Socket;

alarm $timeout                              if $timeout;

$port =~ /^\d+$/ or $noport                     or die "Usage: $0 -port=PORT COMMAND\n";

if ($port)
{
    # To work with OpenBSD: change to
    # $addr = pack(CnC, 127, 0, 1);
    # but make sure to use different ports across different users.
    # (Thanks to  www.gotati.com .)
    $addr = pack(CnC, 127, $<, 1);
    print "solo: bind ", join(".", unpack(C4, $addr)), ":$port\n"   if $verbose;

    $^F = 10;           # unset close-on-exec

    socket(SOLO, PF_INET, SOCK_STREAM, getprotobyname('tcp'))       or die "socket: $!";
    bind(SOLO, sockaddr_in($port, $addr))               or $silent? exit: die "solo($port): $!\n";
}

sleep $sleep if $sleep;

exec @ARGV;

对于Mac OSX,solo(3801): Can't assign requested address除非您0为method的第3个参数强制输入a ,否则它将失败并显示错误pack。对BSD有好处的对Mac也有好处。
埃里克·莱斯钦斯基

1

你需要一把锁。run-one可以完成这项工作,但您可能还需要flockutil-linux软件包中进行调查。

它是内核开发人员提供的标准软件包,比允许更多的自定义,run-one并且仍然非常简单。


0

来自bash-hackers.org的一个对我有用的简单解决方案是使用mkdir。这是一种简单的方法,可确保仅运行程序的一个实例。使用mkdir .lock创建一个目录,该目录返回

  • 如果创建成功,则为true,并且
  • 如果锁定文件存在,则返回false,指示当前正在运行一个实例。

因此,这个简单的功能完成了所有文件锁定逻辑:

if mkdir .lock; then
    echo "Locking succeeded"
    eval startYourProgram.sh ;
else
    echo "Lock file exists. Program already running? Exit. "
    exit 1
fi


echo "Program finished, Removing lock."
rm -r .lock

0

此解决方案适用于需要自我检查的bash脚本

v=$(pgrep -xf "/bin/bash $0 $@")
[ "${v/$BASHPID/}" != "" ] && exit 0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.