如果进程死了,我该如何编写bash脚本来重新启动进程?


226

我有一个python脚本,它将检查队列并对每个项目执行操作:

# checkqueue.py
while True:
  check_queue()
  do_something()

我如何编写一个bash脚本来检查它是否正在运行,如果没有运行,请启动它。大致如下的伪代码(或者也许应该做类似的事情ps | grep?):

# keepalivescript.sh
if processidfile exists:
  if processid is running:
     exit, all ok

run checkqueue.py
write processid to processidfile

我会从crontab调用它:

# crontab
*/5 * * * * /path/to/keepalivescript.sh

4
只是为了添加此为2017年。使用有监督。crontab并不意味着要执行此类任务。bash脚本发出真正的错误很可怕。 stackoverflow.com/questions/9301494/…–
mootmoot

如何使用inittab和respawn代替其他非系统解决方案?参见superuser.com/a/507835/116705
Lars Nordin

Answers:


635

避免使用PID文件,Crons或其他任何尝试评估不是其子进程的过程。

有一个很好的理由说明为什么在UNIX中,您只能等待孩子。试图解决该问题的任何方法(ps解析,pgrep,存储PID等)都存在缺陷,并且存在漏洞。只是说没有

相反,您需要监视您的流程的流程成为流程的父级。这是什么意思?这意味着只有启动您的过程的过程才能可靠地等待其结束。在bash中,这绝对是微不足道的。

until myserver; do
    echo "Server 'myserver' crashed with exit code $?.  Respawning.." >&2
    sleep 1
done

上面的bash代码myserveruntil循环运行的。第一行开始myserver并等待其结束。结束时,until检查其退出状态。如果退出状态为0,则表示退出正常结束(表示您要求退出以某种方式关闭,并且成功完成了退出操作)。在那种情况下,我们不想重新启动它(我们只是要求它关闭!)。如果退出状态不是 0until将运行循环主体,该循环主体将在STDERR上发出错误消息,并在1秒后重新启动循环(返回第1行)。

我们为什么要等一秒钟?因为如果启动顺序有问题,myserver并且立即崩溃,您将有一个非常密集的循环,不断不断地重启和崩溃。这样sleep 1就消除了压力。

现在,您所需要做的就是启动此bash脚本(可能是异步启动),myserver它将根据需要监视并重新启动它。如果要在启动时启动监视器(使服务器“存活”重新启动),则可以使用@reboot规则在用户的cron(1)中计划它。使用以下命令打开您的cron规则crontab

crontab -e

然后添加一条规则以启动监视脚本:

@reboot /usr/local/bin/myservermonitor

或者 查看inittab(5)和/ etc / inittab。您可以在其中添加一行以myserver从某个初始级别开始并自动重生。


编辑。

让我添加一些有关为什么使用PID文件的信息。虽然它们很受欢迎;它们也非常有缺陷,没有理由不以正确的方式来做。

考虑一下:

  1. PID回收(杀死错误的过程):

    • /etc/init.d/foo start:开始foo,将fooPID 写入/var/run/foo.pid
    • 过了一会儿:foo以某种方式死亡。
    • 不久之后:任何开始(称为bar)的随机过程都将采用随机PID,想象一下采用了foo旧的PID。
    • 您会注意到它foo已经消失了:/etc/init.d/foo/restart阅读/var/run/foo.pid,检查它是否还活着,发现bar,认为它foo,杀死它,重新开始foo
  2. PID文件过时。您需要使用过于复杂(或者应该说很简单)的逻辑来检查PID文件是否陈旧,并且任何此类逻辑都容易受到的影响1.

  3. 如果您甚至没有写访问权限或处于只读环境,该怎么办?

  4. 这是毫无意义的过度复杂化;看我上面的例子有多简单。根本不需要使事情复杂化。

另请参阅:“正确”执行时,PID文件是否仍然存在缺陷?

顺便说说; 比解析PID文件还要糟糕ps 永远不要这样做。

  1. ps是非常便携的。在几乎所有UNIX系统上都可以找到它;如果您需要非标准输出,则其参数差异很大。标准输出仅供人类使用,而不用于脚本分析!
  2. 解析ps会导致很多误报。以这个ps aux | grep PID例子为例,现在想象某人以某个地方的数字作为参数开始一个过程,该过程恰好与您启动守护程序的PID相同!想象两个人开始X会话,而您为X拼命杀死了您的X。只是种种不好。

如果您不想自己管理流程;有一些非常好的系统可以充当您的过程的监视器。例如,看看runit


1
@Chas。Ownes:我认为这不是必需的。没有充分的理由,它只会使实现复杂化。简单永远是最重要的。如果它经常重新启动,则休眠将使它不会对系统资源造成任何不良影响。无论如何已经有一条消息。
lhunath

2
@orschiro程序运行时没有资源消耗。如果在启动时立即存在,并且连续不断,则睡眠1的资源消耗仍然可以忽略不计。
lhunath13年

7
可以相信我只是看到这个答案。非常感谢!
getWeberForStackExchange

2
@TomášZato您可以在不测试进程退出代码的情况下执行上述循环,while true; do myprocess; done但是请注意,现在无法停止该进程。
2014年

2
@ SergeyP.akaazure强迫父母在bash出口处杀死孩子的唯一方法是将孩子变成工作trap 'kill $(jobs -p)' EXIT; until myserver & wait; do sleep 1; done
并发

33

看一下monit(http://mmonit.com/monit/)。它处理脚本的启动,停止和重新启动,并且可以进行运行状况检查以及必要时重新启动。

或做一个简单的脚本:

while true
do
/your/script
sleep 1
done

4
Monit正是您想要的。
萨尔克(Sarke)2015年

4
“ while 1”不起作用。您需要“ while [1]”或“ while true”或“ while:”。参见unix.stackexchange.com/questions/367108/what-does-while-mean
Curtis Yallop

8

最简单的方法是在文件上使用群集。在Python脚本中,您可以

lf = open('/tmp/script.lock','w')
if(fcntl.flock(lf, fcntl.LOCK_EX|fcntl.LOCK_NB) != 0): 
   sys.exit('other instance already running')
lf.write('%d\n'%os.getpid())
lf.flush()

在shell中,您实际上可以测试它是否正在运行:

if [ `flock -xn /tmp/script.lock -c 'echo 1'` ]; then 
   echo 'it's not running'
   restart.
else
   echo -n 'it's already running with PID '
   cat /tmp/script.lock
fi

但是当然您不必测试,因为如果它已经在运行并重新启动,它将退出并显示 'other instance already running'

进程终止时,将关闭所有文件描述符,并自动删除所有锁。


可以想象,通过删除bash脚本可以稍微简化一下。如果python脚本崩溃怎么办?文件已解锁?
汤姆(Tom)2009年

1
一旦应用程序停止,文件锁定就会被释放,无论是由于自然终止还是崩溃。
Christian Witts 2009年

@Tom ...更精确一点-一旦文件句柄关闭,该锁就不再处于活动状态。如果Python脚本永远不会因意图而关闭文件句柄,并确保不会通过垃圾回收文件对象自动关闭文件句柄,那么它的关闭很可能意味着脚本已退出/被杀死。即使重新启动等也可以使用。
查尔斯·达菲

1
有更好的使用方法flock...实际上,手册页明确地演示了如何!exec {lock_fd}>/tmp/script.lock; flock -x "$lock_fd"是等同于Python的bash,并保留该锁(因此,如果您随后执行一个进程,则该锁将一直保持到该进程退出之前)。
查尔斯·达菲

我拒绝了您的投票,因为您的代码错误。使用flock是正确的方法,但是您的脚本是错误的。您需要在crontab中设置的唯一命令是:flock -n /tmp/script.lock -c '/path/to/my/script.py'
Rutrus

6

您应该使用monit,这是一种标准的unix工具,可以监视系统上的不同内容并做出相应的反应。

从文档中:http : //mmonit.com/monit/documentation/monit.html#pid_testing

使用pidfile /var/run/checkqueue.pid检查进程checkqueue.py
       如果更改了pid,则执行“ checkqueue_restart.sh”

您还可以将monit配置为在重启时通过电子邮件发送给您。


2
Monit是一个很棒的工具,但是从POSIX或SUSV中指定的形式上来说,它不是标准的。
Charles Duffy 2014年

5
if ! test -f $PIDFILE || ! psgrep `cat $PIDFILE`; then
    restart_process
    # Write PIDFILE
    echo $! >$PIDFILE
fi

很酷,这很好地充实了我的一些伪代码。两个qns:1)如何生成PIDFILE?2)什么是psgrep?它不在ubuntu服务器上。
汤姆

ps grep只是一个与相同的小应用程序ps ax|grep ...。您可以直接安装它或为此编写一个函数:function psgrep(){ps ax | grep -v grep | grep -q“ $ 1”}
soulmerge

只是注意到我没有回答您的第一个问题。
soulmerge

7
在非常繁忙的服务器上,PID可能会在您检查之前被回收。
vartec

2

我不确定它在操作系统之间的可移植性,但是您可能会检查系统是否包含“ run-one”命令,即“ man run-one”。具体来说,这组命令包括“恒定运行”,这似乎正是需要的。

从手册页:

连续运行命令[ARGS]

注意:很明显,这可以从您的脚本中调用,但也完全不需要脚本。


与接受的答案相比,这有什么好处吗?
人间

1
是的,我认为使用内置命令比编写执行相同操作(作为系统代码库的一部分必须维护)的Shell脚本更可取。即使功能是外壳程序脚本的一部分,也可以使用上述命令,因此它与外壳程序脚本问题有关。
丹尼尔·布拉德利

这不是“内置”的;如果默认情况下在某个发行版上安装了该发行版,则您的答案可能应该指定该发行版(如果不是您的发行版,则最好包含一个指向下载位置的指针)。
Tripleee '18

看起来它是一个Ubuntu实用程序;但是即使在Ubuntu上它也是可选的。manpages.ubuntu.com/manpages/bionic/man1/run-one.1.html
Tripleee,

值得注意的是:“运行一”实用程序完全按照其名称所说的运行-您只能运行以run-one-nnnnn运行的任何命令的一个实例。这里的其他答案与可执行程序无关,它们根本不关心命令的内容。
David Kohen

1

我在众多服务器上成功使用了以下脚本:

pid=`jps -v | grep $INSTALLATION | awk '{print $1}'`
echo $INSTALLATION found at PID $pid 
while [ -e /proc/$pid ]; do sleep 0.1; done

笔记:

  • 它正在寻找一个Java进程,因此我可以使用jps,这在发行版中比ps更加一致
  • $INSTALLATION 包含足够的流程路径,这是完全明确的
  • 在等待进程终止时使用睡眠,避免浪费资源:)

该脚本实际上是用来关闭正在运行的tomcat实例的,我想在命令行中关闭(并等待),因此,作为子进程启动它根本不是我的选择。


1
grep | awk仍然是一种反模式 -您想awk "/$INSTALLATION/ { print \$1 }"将无用的东西grep合并到Awk脚本中,该脚本可以很好地通过正则表达式本身找到行,非常感谢。
2015年

0

我将此用于我的npm流程

#!/bin/bash
for (( ; ; ))
do
date +"%T"
echo Start Process
cd /toFolder
sudo process
date +"%T"
echo Crash
sleep 1
done
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.