Linux - 通过操作文件锁来实现shell script进程单实例

Posted 王万林 Ben

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux - 通过操作文件锁来实现shell script进程单实例相关的知识,希望对你有一定的参考价值。

Linux - 通过操作文件锁来实现shell script进程单实例

需求描述

在日常的工作中,经常遇到这样的场景:一个脚本程序,一次只能允许其运行一个实例。
如采集系统指标,传输到日志存储系统。如果脚本同时运行了多个实例,那么可能会导致数据在存储系统中储存了多份重复的(片段)数据。
为了解决这个问题,我们提出了一个需求:这种程序,我们希望在一台服务器上运行时,有且仅有一个实例在运行。

需求分析

由于实例对应的就是程序的(单个或多个)进程,我最初想到的就是判断当前用户是否有command为当前程序名称的进程存在,如果有则直接exit,如果没有则运行。这个也是大家常常使用的一个方法,所以本文不讨论该方法。

另一个方法是通过操作与检测文件锁,来决定是否要运行该程序。

资料查看

使用flock从shell操作锁文件,再加入一些业务逻辑达到效果。
我们查看flock手册:

$ man flock
... snippet ommitted ...

       [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
              This  is useful boilerplate code for shell scripts.  Put it at the top of the shell script you want to lock and
              it'll automatically lock itself on the first run.  If the env var $FLOCKER is not set to the shell script  that
              is  being  run, then execute flock and grab an exclusive non-blocking lock (using the script itself as the lock
              file) before re-execing itself with the right arguments.  It also sets the FLOCKER env var to the  right  value
              so it doesn't run again.
              
... snippet ommitted ...

这里举了一个列子,如果想实现shell script的进程单实例,可以加上上述那行代码。

代码实现

[thesre@centos8 ~]$ cat flock_test.sh 
#!/bin/bash -f

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

echo "I'm running ..."
sleep 3000

效果验证

[thesre@centos8 ~]$ /home/thesre/flock_test.sh & #运行第一个进程
[1] 15112
[thesre@centos8 ~]$ I'm running ...

[thesre@centos8 ~]$ /home/thesre/flock_test.sh & #运行第二个进程
[2] 15136
[thesre@centos8 ~]$ 
[2]    Exit 1                        /home/thesre/flock_test.sh #第二个进程随即退出
[thesre@centos8 ~]$ jobs #再查看发现仅剩第一次运行的进程
[1]  + Running                       /home/thesre/flock_test.sh

延伸

我们看下运行脚本后,操作文件锁的情况。

[thesre@centos8 ~]$ /home/thesre/flock_test.sh &
[1] 19251
[thesre@centos8 ~]$ I'm running ...

[thesre@centos8 ~]$ lslocks #可以看到是19251进程施加的WRITE锁
COMMAND           PID  TYPE SIZE MODE  M START END PATH
(unknown)         545 FLOCK   0B WRITE 0     0   0 /run
master            985 FLOCK   0B WRITE 0     0   0 /
master            985 FLOCK   0B WRITE 0     0   0 /
atd              1015 POSIX   0B WRITE 0     0   0 /run
crond            8083 FLOCK   0B WRITE 0     0   0 /run
AliSecGuard      8518 FLOCK   0B WRITE 0     0   0 /
assist_daemon   19135 FLOCK   0B WRITE 0     0   0 /
aliyun-service  30874 POSIX   0B WRITE 0     0   0 /
(unknown)        6083 FLOCK   0B WRITE 0     0   0 /
(unknown)        6165 FLOCK   0B WRITE 0     0   0 /
flock           19251 FLOCK 131B WRITE 0     0   0 /home/thesre/flock_test.sh
[thesre@centos8 ~]$ cat /proc/locks | grep 19251 #可以看到这个锁的类型是ADVISORY的WRITE锁,还有这个文件所在的主设备号、次设备号以及inode号。
1: FLOCK  ADVISORY  WRITE 19251 fd:01:1334911 0 EOF
[thesre@centos8 ~]$ 
[thesre@centos8 ~]$ ~/strace_cmd_or_pid.sh /home/thesre/flock_test.sh
Trapped CTRL-C
Log direcotry: /tmp/thesre_2021-08-15_12:42:17
[thesre@centos8 ~]$ cd /tmp/thesre_2021-08-15_12:42:17
[thesre@centos8 thesre_2021-08-15_12:42:17]$ ll
total 168
-rw-r--r-- 1 thesre thesre 43706 Aug 15 12:42 _home_thesre_flock_test.sh.19840
-rw-r--r-- 1 thesre thesre  2639 Aug 15 12:42 _home_thesre_flock_test.sh.19841
-rw-r--r-- 1 thesre thesre  2639 Aug 15 12:42 _home_thesre_flock_test.sh.19842
-rw-r--r-- 1 thesre thesre  2639 Aug 15 12:42 _home_thesre_flock_test.sh.19843
-rw-r--r-- 1 thesre thesre  2639 Aug 15 12:42 _home_thesre_flock_test.sh.19844
-rw-r--r-- 1 thesre thesre  3774 Aug 15 12:42 _home_thesre_flock_test.sh.19845
-rw-r--r-- 1 thesre thesre 73522 Aug 15 12:42 _home_thesre_flock_test.sh.19846
-rw-r--r-- 1 thesre thesre  2577 Aug 15 12:42 _home_thesre_flock_test.sh.19847
-rw-r--r-- 1 thesre thesre  6039 Aug 15 12:42 _home_thesre_flock_test.sh.19848
-rw-r--r-- 1 thesre thesre 17989 Aug 15 12:42 _home_thesre_flock_test.sh.19849
[thesre@centos8 thesre_2021-08-15_12:42:17]$ grep flock\\( -r #可以看到flock系统调用尝试锁/home/thesre/flock_test.sh失败。
_home_thesre_flock_test.sh.19840:12:42:17.648474 flock(3</home/thesre/flock_test.sh>, LOCK_EX|LOCK_NB) = -1 EAGAIN (Resource temporarily unavailable) <0.000020>

参考资料

https://github.com/karelzak/util-linux/blob/master/sys-utils/flock.c #flock的源码,感兴趣的同学可以深入研读,主要原理是操作Linux的文件锁。

以上是关于Linux - 通过操作文件锁来实现shell script进程单实例的主要内容,如果未能解决你的问题,请参考以下文章

unix下KSH中shell的SED命令怎样把文件中的NULL替换成空格。 (十六进制码00替换成20)

PHP使用flock实现文件加锁来防止多进程同时写入文件

linux自学_shell理论基础

如何在Windows实现远程调用Linux下的shell指令

Linux脚本shell编程通过数组实现石头剪刀布小游戏

Linux脚本shell编程通过数组实现石头剪刀布小游戏