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)