max*_*zig 71 cron nfs shell-script coreutils lock
有时您必须确保同一时间只有一个 shell 脚本实例正在运行。
例如,通过 crond 执行的 cron 作业本身不提供锁定(例如默认的 Solaris crond)。
实现锁定的常见模式是这样的代码:
#!/bin/sh
LOCK=/var/tmp/mylock
if [ -f $LOCK ]; then # 'test' -> race begin
echo Job is already running\!
exit 6
fi
touch $LOCK # 'set' -> race end
# do some work
rm $LOCK
Run Code Online (Sandbox Code Playgroud)
当然,这样的代码有竞争条件。有一个时间窗口,其中两个实例的执行都可以在第 3 行之后才能访问$LOCK
文件。
对于 cron 作业,这通常不是问题,因为两次调用之间有几分钟的间隔。
但是事情可能会出错——例如,当锁定文件在 NFS 服务器上时——挂起。在这种情况下,多个 cron 作业可以在第 3 行阻塞并排队。如果NFS服务器再次处于活动状态,那么你已经惊群并行运行的作业。
在网上搜索我发现工具lockrun似乎是该问题的一个很好的解决方案。使用它,您可以运行一个需要像这样锁定的脚本:
$ lockrun --lockfile=/var/tmp/mylock myscript.sh
Run Code Online (Sandbox Code Playgroud)
您可以将其放在包装器中或从您的 crontab 中使用它。
lockf()
如果可用,它使用(POSIX) 并回退到flock()
(BSD)。并且lockf()
对 NFS 的支持应该相对广泛。
有替代品lockrun
吗?
其他 cron 守护进程呢?是否有常见的 crond 支持以理智的方式锁定?快速查看 Vixie Crond 的手册页(Debian/Ubuntu 系统上的默认设置)不会显示任何有关锁定的信息。
难道是一个好主意,包括像一个工具lockrun
进入的coreutils?
在我看来,它实现了一个与timeout
,nice
和朋友非常相似的主题。
Tim*_*edy 51
这是在 shell 脚本中进行锁定的另一种方法,可以防止上面描述的竞争条件,其中两个作业都可能通过第 3 行。该noclobber
选项将在 ksh 和 bash 中起作用。不要使用,set noclobber
因为您不应该在 csh/tcsh 中编写脚本。;)
lockfile=/var/tmp/mylock
if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null; then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
# do stuff here
# clean up after yourself, and release your trap
rm -f "$lockfile"
trap - INT TERM EXIT
else
echo "Lock Exists: $lockfile owned by $(cat $lockfile)"
fi
Run Code Online (Sandbox Code Playgroud)
YMMV 锁定 NFS(您知道,当 NFS 服务器无法访问时),但总的来说它比以前更健壮。(10年前)
如果您有从多个服务器同时执行相同操作的 cron 作业,但您只需要实际运行 1 个实例,这样的操作可能适合您。
我没有使用 lockrun 的经验,但是在脚本实际运行之前拥有一个预设的锁定环境可能会有所帮助。或者它可能不会。您只是在包装器中为脚本外部的锁文件设置测试,从理论上讲,如果 lockrun 同时调用两个作业,您不能遇到相同的竞争条件,就像“内部-脚本的解决方案?
无论如何,文件锁定在很大程度上尊重系统行为,并且任何在运行之前不检查锁定文件是否存在的脚本都将执行它们将要执行的任何操作。仅仅通过进行锁定文件测试和正确的行为,您将解决 99% 的潜在问题,如果不是 100%。
如果您经常遇到锁文件争用情况,这可能表明存在更大的问题,例如您的工作时间安排不正确,或者如果时间间隔不如工作完成那么重要,那么您的工作可能更适合进行守护进程.
底座下面@Clint Pachl的评论,如果你使用ksh88,使用mkdir
代替noclobber
。这主要减轻了潜在的竞争条件,但并没有完全限制它(尽管风险很小)。有关更多信息,请阅读Clint 在下面发布的链接。
lockdir=/var/tmp/mylock
pidfile=/var/tmp/mylock/pid
if ( mkdir ${lockdir} ) 2> /dev/null; then
echo $$ > $pidfile
trap 'rm -rf "$lockdir"; exit $?' INT TERM EXIT
# do stuff here
# clean up after yourself, and release your trap
rm -rf "$lockdir"
trap - INT TERM EXIT
else
echo "Lock Exists: $lockdir owned by $(cat $pidfile)"
fi
Run Code Online (Sandbox Code Playgroud)
而且,作为一个额外的优势,如果您需要在脚本中创建 tmpfiles,您可以使用lockdir
它们的目录,知道它们将在脚本退出时被清除。
对于更现代的 bash,顶部的 noclobber 方法应该是合适的。
Arc*_*ege 15
我更喜欢使用硬链接。
lockfile=/var/lock/mylock
tmpfile=${lockfile}.$$
echo $$ > $tmpfile
if ln $tmpfile $lockfile 2>&-; then
echo locked
else
echo locked by $(<$lockfile)
rm $tmpfile
exit
fi
trap "rm ${tmpfile} ${lockfile}" 0 1 2 3 15
# do what you need to
Run Code Online (Sandbox Code Playgroud)
硬链接在 NFS 上是原子的,并且在大多数情况下,mkdir 也是. 在实用层面上使用mkdir(2)
或link(2)
大致相同;我只是更喜欢使用硬链接,因为 NFS 的更多实现允许原子硬链接而不是 atomic mkdir
。使用 NFS 的现代版本,您不必担心使用它们。
gle*_*man 13
我知道这mkdir
是原子的,所以也许:
lockdir=/var/tmp/myapp
if mkdir $lockdir; then
# this is a new instance, store the pid
echo $$ > $lockdir/PID
else
echo Job is already running, pid $(<$lockdir/PID) >&2
exit 6
fi
# then set traps to cleanup upon script termination
# ref http://www.shelldorado.com/goodcoding/tempfiles.html
trap 'rm -r "$lockdir" >/dev/null 2>&1' 0
trap "exit 2" 1 2 3 13 15
Run Code Online (Sandbox Code Playgroud)
一种简单的方法是使用lockfile
通常随procmail
包一起提供的方法。
LOCKFILE="/tmp/mylockfile.lock"
# try once to get the lock else exit
lockfile -r 0 "$LOCKFILE" || exit 0
# here the actual job
rm -f "$LOCKFILE"
Run Code Online (Sandbox Code Playgroud)
小智 8
sem
作为 GNUparallel
工具的一部分提供的可能正是您正在寻找的:
sem [--fg] [--id <id>] [--semaphoretimeout <secs>] [-j <num>] [--wait] command
Run Code Online (Sandbox Code Playgroud)
如:
sem --id my_semaphore --fg "echo 1 ; date ; sleep 3" &
sem --id my_semaphore --fg "echo 2 ; date ; sleep 3" &
sem --id my_semaphore --fg "echo 3 ; date ; sleep 3" &
Run Code Online (Sandbox Code Playgroud)
输出:
1
Thu 10 Nov 00:26:21 UTC 2016
2
Thu 10 Nov 00:26:24 UTC 2016
3
Thu 10 Nov 00:26:28 UTC 2016
Run Code Online (Sandbox Code Playgroud)
请注意,不能保证顺序。此外,在完成之前不会显示输出(令人讨厌!)。但即便如此,这是我所知道的防止并发执行的最简洁的方法,无需担心锁定文件、重试和清理。
小智 5
我使用命令行工具flock
来管理 bash 脚本中的锁,如此处和此处所述。它包含在 Debian 软件包中util-linux
,并且默认安装在许多 Linux 系统上。
以下是如何将它用于 cron 作业:
* * * * * flock -n /tmp/foobar /usr/bin/foobar arg1 arg2 arg3
Run Code Online (Sandbox Code Playgroud)
如果文件/tmp/foobar
尚不存在,这将创建该文件,尝试获取该文件的独占锁,运行/usr/bin/foobar arg1 arg2 arg3
,然后释放该锁,并使用命令的退出代码退出。如果无法获得锁定,则flock
由于-n
选项( 的缩写--non-block
),该命令会立即失败并返回 1 退出代码。
我使用了flock手册页中的这个简单方法,在子shell中运行一些命令......
(
flock -n 9
# ... commands executed under lock ...
) 9>/var/lock/mylockfile
Run Code Online (Sandbox Code Playgroud)
在该示例中,如果无法获取锁定文件,则会失败并退出代码为 1。