防止重复的Cron作业运行


92

我已经安排了一个cron作业每分钟运行一次,但是有时脚本需要花费一分钟以上的时间才能完成,而且我不希望这些作业彼此开始“堆叠”。我猜这是一个并发问题-即脚本执行需要互斥。

为了解决该问题,我让脚本查找了特定文件(“ lockfile.txt ”)的存在touch,如果存在则退出,否则退出。但这是一个很糟糕的信号灯!我是否应该了解最佳实践?我应该改写一个守护进程吗?

Answers:


118

有一些程序可以自动执行此功能,自己消除烦恼和潜在的错误,还可以通过在后台使用植绒来避免过时的锁定问题(如果您只是使用触摸,则存在风险) 。我用lockrunlckdo过去一样,但现在有flock(1)(在UTIL-linux下的新望版本),这是伟大的。真的很容易使用:

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job

2
由于flock(1)在util-linux中,因此lckdo将被从moreutils中删除。而且该软件包在Linux系统中基本上是必需的,因此您应该能够依靠它的存在。有关用法,请看下面。
jldugger 2012年

是的,羊群现在是我的首选。我什至会更新我的答案以适合。
womble

有谁知道之间的区别flock -n file commandflock -n file -c command
Nanne 2015年

2
@Nanne,我必须检查代码以确保确定,但是我的有根据的猜测是,-c通过外壳程序运行了指定的命令(按照联机帮助页),而“裸”(非-c)形式只是exec给定的命令。在shell中放一些东西可以使您做类似shell的事情(例如运行多个用;或分隔的命令&&),但是如果您使用的是不受信任的输入,也使您容易受到shell扩展攻击。
womble

1
这是(假设的)frequent_cron_job命令的一个参数,试图显示它每分钟都在运行。我删除了它,因为它没有添加任何有用的信息,并引起了混乱(您的想法,如果多年来没有其他人的话)。
womble

28

在shell中最好的方法是使用flock(1)

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock

1
我不能反对使用fd重定向的棘手问题。太神奇了。
womble

1
不需要在Bash或ZSH中为我解析,需要消除它们之间的距离99>所以是99> /...
Kyle Brandt

2
@Javier:这并不意味着它不是棘手的和不可思议的,只是它已被记录,棘手和不可思议的。
womble

1
如果在运行时重新启动或以某种方式终止进程,将会发生什么?那会永远锁定吗?
Alex R

5
我知道此结构会创建一个排他锁,但我不了解如何完成此操作的机制。这个答案中“ 99”的功能是什么?有人在乎解释吗?谢谢!
Asciiom '16

22

实际上,flock -n可以使用lckdo* 代替*,因此您将使用内核开发人员提供的代码。

womble的示例为基础,您将编写如下内容:

* * * * * flock -n /some/lockfile command_to_run_every_minute

BTW,看代码,所有的flocklockrunlckdo做同样的事情,所以它只是一个问题,其中最容易提供给您。


2

您可以使用锁定文件。在脚本启动时创建此文件,在脚本结束时将其删除。该脚本在运行其主例程之前,应检查锁定文件是否存在,并相应地进行操作。

锁定文件由initscripts以及Unix系统中的许多其他应用程序和实用程序使用。


1
这是我亲自看到的唯一实现方式。根据维护者的建议,我将其用作OSS项目的镜像
沃伦(Warren)

2

您尚未指定是否要脚本等待上一次运行完成。通过“我不希望作业开始彼此“堆叠””,我想您是在暗示您希望脚本在已经运行时退出,

因此,如果您不想依赖lckdo或类似的东西,可以这样做:


PIDFILE=/tmp/`basename $0`.pid

if [ -f $PIDFILE ]; then
  if ps -p `cat $PIDFILE` > /dev/null 2>&1; then
      echo "$0 already running!"
      exit
  fi
fi
echo $$ > $PIDFILE

trap 'rm -f "$PIDFILE" >/dev/null 2>&1' EXIT HUP KILL INT QUIT TERM

# do the work


谢谢,您的示例很有帮助-如果脚本已经运行,我希望它退出。感谢您提及ickdo-它似乎可以解决问题。
汤姆

FWIW:我喜欢这种解决方案,因为它可以包含在脚本中,因此无论调用脚本的方式如何,锁定均有效。
David G

1

这也可能表明您做错了事。如果您的工作运行得如此频繁且如此频繁,也许您应该考虑取消克隆它,并使其成为守护程序风格的程序。


3
我对此表示不同意。如果您需要定期运行某些程序,则将其设置为守护程序是“坚果的大锤”解决方案。使用锁文件来预防事故是我从未遇到过的一个完美合理的解决方案。
womble

@womble我同意;但是我喜欢用大锤砸坚果!:-)
wzzrd

1

如果您的cron守护程序的先前实例仍在运行,则不应调用它们。我是一个cron守护程序dcron的开发人员,我们专门尝试防止这种情况。我不知道Vixie cron或其他守护程序如何处理此问题。


1

我建议使用运行命令-比处理锁简单得多。从文档:

run-one是一个包装脚本,运行的脚本最多包含一个带有一组唯一参数的命令的唯一实例。当您一次只需要运行一个副本时,这对于cronjobs通常很有用。

run-this-one与run-one完全一样,不同之处在于它将使用pgrep和kill来查找和杀死用户拥有并匹配目标命令和参数的所有正在运行的进程。请注意,在尝试杀死匹配的进程时,运行本将阻塞,直到所有匹配的进程都死掉为止。

运行一恒定地运行与运行一完全相同,不同之处在于运行COMMAND退出时(零或非零)都会重新生成“ COMMAND [ARGS]”。

保持运行一次是恒定运行一次的别名。

运行一直到成功与运行一恒定运行完全相同,只是它重新生成“ COMMAND [ARGS]”,直到COMMAND成功退出(即退出零)为止。

运行一直到失败的操作与运行一不变的操作完全相同,只是它重新生成“ COMMAND [ARGS]”,直到COMMAND失败退出(即退出非零)为止。


1

现在systemd已经发布,Linux系统上还有另一种调度机制:

一种 systemd.timer

/etc/systemd/system/myjob.service~/.config/systemd/user/myjob.service

[Service]
ExecStart=/usr/local/bin/myjob

/etc/systemd/system/myjob.timer~/.config/systemd/user/myjob.timer

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

如果在下次激活计时器时服务单元已经处于激活状态,则该服务的另一个实例将不会启动。

另一种方法是,在启动时启动一次作业,每次运行结束后一分钟启动一次作业:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target

0

我创建了一个jar来解决这样的问题,例如重复的cron正在运行,可能是java或shell cron。只需在Duplicates.CloseSessions(“ Demo.jar”)中传递cron名称,即可搜索并杀死该cron的existng pid(当前除外)。我已经实现了执行此操作的方法。字符串proname = ManagementFactory.getRuntimeMXBean()。getName(); 字符串pid = proname.split(“ @”)[0]; System.out.println(“当前PID:” + pid);

            Process proc = Runtime.getRuntime().exec(new String[]{"bash","-c"," ps aux | grep "+cronname+" | awk '{print $2}' "});

            BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String s = null;
            String killid="";

            while ((s = stdInput.readLine()) != null ) {                                        
                if(s.equals(pid)==false)
                {
                    killid=killid+s+" ";    
                }
            }

然后使用shell命令再次杀死killid字符串


我认为这并不能真正回答问题。
kasperd '16

0

无论如何,@ Philip Reynolds的答案将在5秒钟的等待时间后开始执行代码,而不会获得锁定。跟随Flock似乎无法正常工作,我修改了@Philip Reynolds的答案

(
  flock -w 5 -x 99 || exit 1
  ## Do your stuff here
) 99>/path/to/my.lock

这样就不会同时执行代码。相反,等待5秒钟后,如果到那时仍未获得锁定,则该过程将以1退出。

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.