如何避免 Cronjob 重複運行

原文地址: https://www.tony-yin.site/201...

cronjob

Cronjob使用中有不少問題須要注意,前段時間寫了一篇文章《爲何 Cronjob 不執行》,裏面談到了各類會致使cronjob不執行的因素和解決方案,而本文就cronjob重複運行的場景,對技術手段、技術方案、具體代碼和相互優劣展開詳細講解。linux

引子

以前寫過一篇文章《Ctdb Rados(二):多場景斷網高可用》,文中提到支持秒級的定時任務的方法,由於cronjob自己最小隻支持分鐘級別的定時任務,因此筆者在cronjob定時腳本中經過for循環來達到秒級定時的目的。shell

然而這種定時間隔很短的任務是很容易出現重複運行的問題的。正常狀況下腳本執行時間是很短的,可是一旦遇到IO阻塞等問題,會出現多個任務同時運行的狀況,這種狀況每每不是咱們所指望的,可能會致使意想不到的問題。bash

即便不是秒級的定時任務,只要任務執行時間超過定時間隔都會出現重複運行的問題,好比每分鐘運行的定時任務,而其執行時間須要三分鐘等等spa

例子以下:.net

$ ps -elf | grep forever
4 S vagrant   4095  4094  0  80   0 -  1111 wait   21:59 ?        00:00:00 /bin/sh -c /var/tmp/forever.sh
0 S vagrant   4096  4095  0  80   0 -  2779 wait   21:59 ?        00:00:00 /bin/bash /var/tmp/forever.sh
4 S vagrant   4100  4099  0  80   0 -  1111 wait   22:00 ?        00:00:00 /bin/sh -c /var/tmp/forever.sh
0 S vagrant   4101  4100  0  80   0 -  2779 wait   22:00 ?        00:00:00 /bin/bash /var/tmp/forever.sh
4 S vagrant   4130  4129  0  80   0 -  1111 wait   22:01 ?        00:00:00 /bin/sh -c /var/tmp/forever.sh
0 S vagrant   4131  4130  0  80   0 -  2779 wait   22:01 ?        00:00:00 /bin/bash /var/tmp/forever.sh
4 S vagrant   4135  4134  0  80   0 -  1111 wait   22:02 ?        00:00:00 /bin/sh -c /var/tmp/forever.sh
0 S vagrant   4136  4135  0  80   0 -  2779 wait   22:02 ?        00:00:00 /bin/bash /var/tmp/forever.sh

解決方案

方案1:進程數

這是筆者第一時間本身想的方式,經過進程數來判斷當前定時腳本同時執行的數量,好比執行的腳本名爲/opt/test.sh,當有一個任務在運行的時候:vagrant

[root@tony ~]# ps -ef | grep /opt/test.sh
root      1107 25880  0 23:26 pts/0    00:00:00 /usr/bin/bash /opt/test.sh
root      1305  1175  0 23:27 pts/5    00:00:00 grep --color=auto /opt/test.sh

此時經過ps -ef | grep /opt/test.sh | wc -l獲得的數量應該是2,若是定時間隔完畢後又刷新了一輪,總進程數則會變成3code

因此咱們能夠在/opt/test.sh中加入進程數的判斷,若是進程數大於2,就說明存在已有任務在運行,此時應該退出執行blog

count=$(ps -ef | grep /opt/test.sh | wc -l)
if [ $count -gt 2 ]; then
    echo "Exist job running!"
    exit 1
fi
do something

可是事與願違,當咱們在/opt/test.sh中經過ps命令獲取定時任務運行數量的時候發現,若是隻存在當前的任務運行時,獲得的進程數是3,若是有其餘一個已在運行,則進程數是4,以此類推。這是爲何呢?進程

通過一番研究發現,當只存在當前任務運行時,若是腳本里面是直接運行ps命令,獲得的進程數是2,以下所示:rem

ps -ef | grep /opt/test.sh | wc -l

不難看出這是$()的緣由,它在shell中起了一個子shell,因此在子shell執行ps的同時多了一個當前腳本任務運行的進程,因此比正常進程數多1,因此上面代碼咱們須要改成:

count=$(ps -ef | grep /opt/test.sh | wc -l)
if [ $count -gt 3 ]; then
    echo "Exist job running!"
    exit 1
fi
do something

方案2:普通文件鎖

能夠經過一個文件來標識當前是否存在任務在運行,具體作法爲當運行任務時,先檢查是否存在文件鎖,若是存在則表示上個任務尚未運行結束,則退出;若是不存在文件鎖,則新建立一個文件鎖,而後執行任務,最後執行完畢後刪除文件鎖。

具體代碼以下:

file_lock=/opt/test.lock
if [ -f file_lock ]; then
    echo "Exist job running!"
    exit 1
fi
touch file_lock
do something
rm -f file_lock

方案3:進程號文件鎖

所謂進程號文件鎖,相比於方案2的普通文件鎖不一樣的地方就是會把當前運行任務對應的進程號寫入鎖文件中,其優點在於除了能夠經過檢查文件是否存在來判斷是否存在已經運行的任務,還能夠再經過鎖文件裏面的進程號來作第二次確認。

也許有人會問這個二次確認有啥用?你還別說,這個還真有用,不少時候進程意外終止或者被手動殺掉後,文件鎖依然存在,那麼使用普通文件鎖的結果就是其實並無正在運行的任務,可是因爲存在文件鎖,以後全部的任務都不會再運行。而進程號文件鎖則能夠在文件鎖判斷以外,再對鎖文件中的進程號進行判斷是否還在運行,具體代碼以下:

#!/bin/bash

PIDFILE=/opt/test.pid
if [ -f $PIDFILE ]
then
  PID=$(cat $PIDFILE)
  ps -p $PID > /dev/null 2>&1
  if [ $? -eq 0 ]
  then
    echo "Exist job running!"
    exit 1
  else
    echo $$ > $PIDFILE
    if [ $? -ne 0 ]
    then
      echo "Could not create PID file!"
      exit 1
    fi
  fi
else
  echo $$ > $PIDFILE
  if [ $? -ne 0 ]
  then
    echo "Could not create PID file!"
    exit 1
  fi
fi

do something
rm $PIDFILE

雖然此方案看起來很完美,可是仍是有一個場景沒有考慮到,那就是若是正在運行任務的進程被kill掉,而後另外一個進程使用了和被kill進程相同的pid,這樣也會致使其實任務並無在運行,因爲存在鎖文件和對應進程號的進程在運行,以後全部的任務再也不運行。雖然這種場景很極端,可是也是有可能出現的,不過不要緊,下面的方案會幫你解決這個問題。

方案4:flock 鎖

linux flock鎖有區別於通常的鎖,它不只僅是檢查文件是否存在,它會一直存在直到進程結束,因此能夠直接地知道進程是否真的執行結束了。

格式:

flock [-sxun][-w #] fd#
flock [-sxon][-w #] file [-c] command

選項:

-s, --shared:    得到一個共享鎖 
 -x, --exclusive: 得到一個獨佔鎖 
 -u, --unlock:    移除一個鎖,腳本執行完會自動丟棄鎖 
 -n, --nonblock:  若是沒有當即得到鎖,直接失敗而不是等待 
 -w, --timeout:   若是沒有當即得到鎖,等待指定時間 
 -o, --close:     在運行命令前關閉文件的描述符號。用於若是命令產生子進程時會不受鎖的管控 
 -c, --command:   在shell中運行一個單獨的命令 
 -h, --help       顯示幫助 
 -V, --version:   顯示版本

鎖類型:

共享鎖:多個進程可使用同一把鎖,常被用做讀共享鎖
獨佔鎖:同時只容許一個進程使用,又稱排他鎖,寫鎖。

這裏因爲咱們只容許同時存在一個任務運行,因此選擇獨佔鎖,而後須要在腳本執行完丟棄鎖:

* * * * *  flock -xn /opt/test.lock -c /opt/test.sh

方案5:solo 程序

Solo是一個Perl腳本,它的工做原理與flock相似,但它並不依賴於鎖文件,由於Solo程序是經過綁定端口來實現。

$ ./solo -port=6000 /opt/test.sh &
[1] 7503
$ ./solo -port=6000 /opt/test.sh
solo(6000): Address already in use

執行solo時,將綁定指定的端口並執行後面指定的命令。一旦命令完成,就會釋放端口,容許任務的下一個調用正常執行。

solo的優點在於沒有人可以經過刪除一個文件並意外地致使任務重複運行。即便使用flock命令,若是鎖文件被刪除,也能夠啓動第二個做業。因爲solo綁定了一個端口,因此不可能出現這種狀況。

總結

上面提到了五種方案,第一種方案略顯粗糙,可是缺陷相對來講較少;第二種方案存在鎖文件被意外刪除或者進程被kill的風險;第三種方案存在鎖文件被意外刪除和新進程佔用相同進程號的問題;第四種方案仍是存在乎外刪除鎖文件的問題;第五種方案則不須要擔憂鎖文件被刪除致使任務重複運行的問題。

目前看起來第五種方案是最優的,不存在缺陷。不過仍是得看具體場景,筆者認爲第三種、第四種、第五種方案都是有可取之處的,你們仍是根據各自的場景選擇最適合本身的方案吧。

Refer

相關文章
相關標籤/搜索