쉘 스크립트의 잠금이 정확합니까?

쉘 스크립트의 잠금이 정확합니까?

때로는 쉘 스크립트의 인스턴스가 한 번에 하나만 실행되는지 확인해야 합니다.

예를 들어, 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

물론 이러한 코드에는 경쟁 조건이 있습니다. 한 인스턴스가 파일에 액세스할 수 있기 전에 두 인스턴스의 실행이 3행 이후로 진행될 수 있는 시간 창이 있습니다 $LOCK.

크론 작업의 경우 호출 사이에 몇 분의 간격이 있기 때문에 이는 일반적으로 문제가 되지 않습니다.

그러나 잠금 파일이 NFS 서버에 있는 경우와 같이 상황이 잘못되어 중단될 수 있습니다. 이 경우 여러 크론 작업이 차단되어 라인 3에 대기할 수 있습니다. NFS 서버가 다시 활성화되면놀라운 무리작업은 병렬로 실행됩니다.

온라인 검색에서 이 도구를 찾았습니다.잠긴 작동이것은 이 문제에 대한 좋은 해결책인 것 같습니다. 이를 사용하면 다음과 같이 잠금이 필요한 스크립트를 실행할 수 있습니다.

$ lockrun --lockfile=/var/tmp/mylock myscript.sh

래퍼에 넣거나 crontab에서 사용할 수 있습니다.

사용 가능한 경우 (POSIX)를 사용 하고 (BSD) lockf()로 대체됩니다 . flock()그리고 lockf()NFS에 대한 지원은 비교적 광범위해야 합니다.

어떤 대안이 있습니까 lockrun?

다른 cron 데몬은 어떻습니까? 합리적인 방식으로 잠금을 수행하기 위한 일반적인 크론드 지원이 있습니까? Vixie Crond(Debian/Ubuntu 시스템의 기본값)의 매뉴얼 페이지를 잠깐 살펴보면 잠금에 대한 정보가 전혀 나와 있지 않습니다.

lockrun유사한 도구를 포함하는 것이 좋은 생각일까요?핵심 도구?

내가 보기엔 그것이 구현하는 테마가 다음과 매우 유사한 것 같습니다.timeout, nice그리고 친구들.

답변1

이는 두 작업이 모두 라인 3을 통과할 수 있는 위에서 설명한 경쟁 조건을 방지하는 쉘 스크립트를 잠그는 또 다른 방법입니다. 이 noclobber옵션은 ksh 및 bash에서 작동합니다. set noclobbercsh/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

YMMV는 NFS를 잠그지만(NFS 서버에 액세스할 수 없는 경우) 전반적으로 이전보다 더 강력해졌습니다. (10년 전)

동시에 동일한 작업을 수행하는 여러 서버의 크론 작업이 있지만 실제로 실행하려면 인스턴스가 하나만 필요한 경우 이와 유사한 작업이 적합할 수 있습니다.

저는 lockrun에 대한 경험이 없지만 스크립트가 실제로 실행되기 전에 잠금 환경을 미리 설정하는 것이 도움이 될 수 있습니다. 아니면 아닐 수도 있습니다. 래퍼의 스크립트 외부에서 잠긴 파일에 대한 테스트를 설정할 수 있으며 이론적으로 두 작업 모두에서 lockrun이 정확히 동시에 호출되면 스크립트의 "내부"와 동일한 경쟁 조건이 발생하게 됩니까? ? 해결책?

그럼에도 불구하고 파일 잠금은 시스템 동작을 거의 존중하며 실행 전에 잠긴 파일이 존재하는지 확인하지 않는 스크립트는 수행하려는 모든 작업을 수행합니다. 잠금 파일 테스트와 올바른 동작만으로 잠재적인 문제의 99%(100%는 아니더라도)를 해결할 수 있습니다.

잠금 파일 경쟁 조건이 자주 발생하는 경우 작업 타이밍이 올바르지 않은 등 더 큰 문제가 있음을 나타낼 수도 있고 간격이 작업 완료만큼 중요하지 않은 경우 작업이 데몬에 더 적합할 수도 있습니다.


다음과 같이 편집됨 - 2016-05-06 (KSH88을 사용하는 경우)


아래 @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

그리고 추가 보너스로 스크립트에서 tmp 파일을 생성해야 하는 경우 lockdir스크립트가 종료되면 해당 파일이 지워진다는 것을 알고 해당 디렉토리를 사용할 수 있습니다.

보다 현대적인 bash를 위해서는 상단의 noclobber 방법이 적합합니다.

답변2

나는 하드 링크를 사용하는 것을 선호합니다.

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

하드 링크는 NFS에서 원자적입니다.그리고 대부분의 경우,mkdir도 마찬가지. 실제 수준에서는 작동하거나 mkdir(2)거의 link(2)동일합니다. 더 많은 NFS 구현이 원자적 하드 링크보다는 원자적 하드 링크를 허용하기 때문에 저는 하드 링크를 사용하는 것을 선호합니다 mkdir. 최신 버전의 NFS를 사용하면 둘 중 하나를 사용하는 것에 대해 걱정할 필요가 없습니다.

답변3

나는 이것이 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

답변4

간단한 방법은 다음과 같습니다.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"

관련 정보