很多情况,当我们的服务进程遇到异常退出了,我们希望它能立即再起来。
大部分情况下,我们可以使用 Shell 脚本来做。写一个 while,定期检查进程还在不在。如果不在了,则立即启动。
类似如下:
while true do
sleep 10
if [ `pgrep my_server` == '' ]; then
my_server -x aa -y bb &
fi
done
这种非常简单,也非常粗暴。
但是,由于它是定期去查的,这个 sleep 的时间有点考究。设备长了,进程退了,最长也要一个sleep周期才能被检查重启。设置小了,CPU会花大量的时候做这个检查,很不值得。
另一种做法呢,就是利用父进程能使用 wait() 获取子进程状态的机制来实现。
父进程使用 fork() 创建了一个子进程,由子进程来跑业务逻辑,而父进程什么都不做,就阻塞调用 wait() 等子进程的状态化。一旦子进程退出或出异常,wait() 函数就会返回,那么父进程就知道子进程退出了。
对于服务器程序来说,服务器退出是不正常的。于是父进程就立即再 fork() 一个新的子进程来路业务逻辑。从而实现了业务进程异常退出立即重启的功能。
如果为核心的代码:
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
void EnableResurrection()
{
pid_t cpid, w;
int wstatus;
while (1) {
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) {
/* Code executed by child */
printf("Child PID is %ldn", (long) getpid());
return;
}
/* Code executed by parent */
do {
w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED);
if (w == -1) {
perror("waitpid");
exit(EXIT_FAILURE);
}
if (WIFEXITED(wstatus)) {
printf("exited, status=%dn", WEXITSTATUS(wstatus));
} else if (WIFSIGNALED(wstatus)) {
printf("killed by signal %dn", WTERMSIG(wstatus));
} else if (WIFSTOPPED(wstatus)) {
printf("stopped by signal %dn", WSTOPSIG(wstatus));
} else if (WIFCONTINUED(wstatus)) {
printf("continuedn");
}
} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
}
}
EnableResurrection() 意为“复活”,其函数实现也非常简单。
函数一直来,就 fork() 一个子进程,让子进程直接返回了。而父进程,就使用 waitpid() 等待子进程退出。一旦父进程等到了子进程退出,就立即再 fork() 一个新的子进程。从而达到了业务进程一旦退出,就立即恢复的效果。
当一个程序需要复活功能的时候,它只需要在真正开始业务之前调用一下该函数,就能神奇地达到业务进程退出立即“复活”的效果。
如下:
void DoSomething()
{
char *dangle_ptr = NULL;
strcpy(dangle_ptr, "hello"); //! 这里会出段错误
}
int main(int argc, char *argv[])
{
EnableResurrection();
while (1) {
sleep(1);
DoSomething();
}
return 0;
}
如上,从业务程序的角度上看,执行完 EnableResurrection() 之后,进程像是保存了一个状态。每当业务进程跑崩了,它都能从 EnableResurrection() 返回处重新运行。
除了上述的功能。我们还可以给它一些附加的功能。比如:异常日志记录、异常统计与分析、异常规避与修复等。