Linux shell脚本单实例运行之flock(1)

1. 简述

github项目地址:https://github.com/superwujc

尊重原创,欢迎转载,注明出处:https://my.oschina.net/superwjc/blog/1810999

某些脚本或程序需要确保单实例运行,如通过cron(8)定时任务执行mysqldump(1)数据库备份,此类任务通常耗时较长,因而每次执行都需占据较多的系统与网络资源,若由于误操作或其他原因而运行了同一任务的多个实例,则将导致不必要的资源浪费,以及其他可能的连锁问题。

shell脚本中可以通过util-linux软件包基于flock(2)系统调用提供的flock(1)工具,以文件锁的方式实现任务的单实例运行;文件锁在Linux系统中的实现可以参照上一篇博文 Linux文件锁实现之flock(2)与fcntl(2)

2. 用法

flock(1)命令包含3种形式:

  1. flock 命令选项 加锁文件或目录 -c 不带参数的命令或脚本
  2. flock 命令选项 加锁文件或目录 不带参数的命令或脚本 命令或脚本的参数
  3. flock 命令选项 引用锁文件的文件描述符

第2种形式中,若命令或脚本的参数为空字符串,则等同于第1种形式。

主要命令行选项与参数包括:

  • -s, --shared:共享锁
  • -x, -e, --exclusive:独占锁,默认类型
  • -u, --unlock:解锁
  • -n, --nb, --nonblock:非阻塞,若指定的文件正在被其他进程锁定,则立即以失败返回
  • -w, --wait, --timeout seconds:若指定的文件正在被其他进程锁定,则等待指定的秒数;指定为0将被视为非阻塞
  • -o, --close:锁定文件后与执行命令前,关闭用于引用加锁文件的文件描述符
  • -E, --conflict-exit-code number:若指定-n时请求加锁的文件正在被其他进程锁定,或指定-w时等待超时,则以该选项的参数作为返回值
  • -c, --command command:运行无参数的命令
  • -h, --help:输出用法信息
  • -V, --version:输出版本信息

标准的util-linux对flock(1)的实现另包含无参数选项-F, --no-fork,允许直接运行指定的命令而不调用fork(2)创建子进程;CentOS7移除了对该选项的支持,总是通过fork(2)创建的子进程运行命令,且父进程调用waitpid(2)等待子进程的退出;由于flock(2)实现的文件锁与系统文件表项相关联,且文件描述符默认随fork(2)继承,因此父子进程中各自包含一个引用同一文件锁的文件描述符,此时需要考虑是否指定-u选项显式解锁,以及是否指定-o选项以关闭子进程中用于引用文件锁的文件描述符。

3. 示例

操作系统与内核版本

# lsb_release -a
LSB Version:	:core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch
Distributor ID:	CentOS
Description:	CentOS Linux release 7.4.1708 (Core)
Release:	7.4.1708
Codename:	Core
# uname -r
3.10.0-693.21.1.el7.x86_64

glibc与gcc版本

# ldd /bin/ls
	linux-vdso.so.1 =>  (0x00007ffdd6fe6000)
	libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fde08885000)
	libcap.so.2 => /lib64/libcap.so.2 (0x00007fde08680000)
	libacl.so.1 => /lib64/libacl.so.1 (0x00007fde08476000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fde080b3000)
	libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fde07e51000)
	libdl.so.2 => /lib64/libdl.so.2 (0x00007fde07c4c000)
	/lib64/ld-linux-x86-64.so.2 (0x000055a89d771000)
	libattr.so.1 => /lib64/libattr.so.1 (0x00007fde07a47000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fde0782b000)
#
# /lib64/libc.so.6
GNU C Library (GNU libc) stable release version 2.17, by Roland McGrath et al.
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.8.5 20150623 (Red Hat 4.8.5-16).
Compiled on a Linux 3.10.0 system on 2017-11-30.
Available extensions:
	The C stubs add-on version 2.1.2.
	crypt add-on version 2.1 by Michael Glad and others
	GNU Libidn by Simon Josefsson
	Native POSIX Threads Library by Ulrich Drepper et al
	BIND-8.2.3-T5B
	RT using linux kernel aio
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.

util-linux工具包版本

# flock -V
flock from util-linux 2.23.2

示例脚本 - single_instance.sh

#! /bin/bash

source /etc/profile

readonly LOCKFILE="/var/run/single_instance.pid"
readonly FD=$(ls -l /proc/$$/fd | sed -n '$p' | awk '{print $9}')
readonly LOCKFD=$(( ${FD}+1 ))

eexit() {
	local error_str="$@"
	echo -e ${error_str}
	exit 1
}

lock() {
	eval "exec ${LOCKFD}>${LOCKFILE}"
	flock -n ${LOCKFD} && echo "${BASHPID}" > "${LOCKFILE}" || eexit "\e[1;31m$0 is already running\x1b[0m"
}

unlock() {
	flock -u ${LOCKFD}
	eval "exec ${LOCKFD}>&-"
}

do_something() {
	sleep 20
	return 0
}

lock
do_something
if [[ $? == 0 ]]; then
	echo success
	unlock
else
	echo failed
fi

程序逻辑:

1. 加锁,通过exec fd>file的方式以指定的文件描述符打开需要加锁的文件,然后调用flock(1)以非阻塞方式对文件设置独占锁,若成功则将进程PID写入到锁文件,若失败则输出错误信息并返回

2. 执行指定任务,本例中为do_something()函数中的sleep;若成功则解锁,失败则输出错误信息

运行脚本

# ./single_instance.sh &
[1] 6585
#
# cat /var/run/single_instance.pid
6585
#
# ./single_instance.sh
./single_instance.sh is already running

flock(1)的手册页中提供了一种更为简洁的方式,用于单实例运行shell脚本,将语句

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

放在脚本的开头,其中$0表示命令或脚本的名称字符串,$@表示命令或脚本的参数字符串列表,单个冒号(':')为shell内置命令,在此表示不执行任何操作。

该行语句可以分解为下列步骤:

  1. 检查变量FLOCKER是否为脚本名称字符串,若不是,则以flock -en "$0" "$0" "$@"替换当前进程,并传递环境变量FLOCKER="$0",导致的结果为,脚本文件自身成为非阻塞独占锁文件,且变量FLOCKER的值为脚本名称字符串。
  2. flock(1)加锁后,以子进程运行"$0" "$@",即再一次重新运行脚本;由于环境变量会被子进程继承,因而此时[ "${FLOCKER}" != "$0" ]为假,||之后的内容将被执行,而单个冒号不执行任何操作,因此该行之后的内容将被执行,且执行期间将一直独占性锁定脚本文件自身。
  3. 再次尝试运行该脚本时,[ "${FLOCKER}" != "$0" ]依然为真,但若已有实例正在运行,则加锁操作将失败,flock(1)以状态码1退出。

转载于:https://my.oschina.net/superwjc/blog/1810999

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值