进程的守护神-daemontools


进程的守护神 - daemontools


前言

我们在日常的开发中可能需要写一些常驻内存的程序常驻内存的程序守护进程程序是不一样的,守护进程的实现步骤稍微麻烦点,感兴趣的读者可以参考我用C实现的一个守护进程daemonize.c,我们重点关注下常驻内存的程序
PHP为例来说常驻内存的程序实现一般是这样的:

首先使用while (1) {}结构使程序无限循环,并且在程序内部对各种可能出现的异常进行捕捉处理,目的是防止程序意外退出。

hello.php

#!/usr/bin/env php
<?php

while (1) {
    try {
        // dostuff();
        printf("hello, %d\n", $i++);
    } catch (Exception $e) {
        // exception handling
    }
    sleep(1);
}

接着,通过命令行把它放到后台执行,例如:

$ nohup php hello.php &

但这种实现方式是有一定的缺陷的,那就是当PHP执行过程中遇到错误的时候,就会退出程序,不官你程序中使用多少层while (1) {},也不管你使用try...catch多少Exception,你还是阻止不了程序的意外退出(比如程序产生奇怪的coredump或者kill时手误杀错了程序)。如果这个常驻内存的程序是一个队列消费者程序,那么这种缺陷是很致命的,因为在流量高峰时如果程序一旦意外退出没有即时恢复,那么将导致队列中的消息一直堆积无法被消费掉,从而影响正常业务流程。再严重点,如果队列没有最大内存限制的策略,那么消息的堆积将会导致内存使用暴涨从而拖垮机器。怎么办?你可以用crontab脚本每分钟监视你的程序,看到没有在执行就启动起来。当然,还有更好的办法就是使用成熟的进程管理工具,它会监控你的程序,一旦发现进程退出了,立刻启动起来。Linux下对常驻内存的进程的管理通常使用DaemontoolsSupervisor 这两个工具。今天我们就来研究学习下Daemontools

介绍

daemontools是用于管理UNIX服务的工具的集合,它分为三类工具:

  • 常驻进程管理工具

The svscanboot program

The svscan program

The supervise program

The svc program

The svok program

The svstat program

The fghack program

The pgrphack program

  • 日志管理工具

The readproctitle program

The multilog program

The tai64n program

The tai64nlocal program

  • 环境管理工具

The setuidgid program

The envuidgid program

The envdir program

The softlimit program

The setlock program

我们重点关注对常驻进程管理工具的使用,日志管理工具和环境管理工具主要是辅助进程管理做一些额外的功能,进程管理工具主要是通过svscanbootsvscansupervisesvcsvoksvstat命令来管理常驻进程的

安装

$ cd /usr/local/software/
$ wget http://cr.yp.to/daemontools/daemontools-0.76.tar.gz
$ tar -zxvf daemontools-0.76.tar.gz -C /usr/local/
$ cd /usr/local/admin/daemontools-0.76/
$ package/install

如果在Linux下安装出现如下错误: 

/usr/bin/ld: errno: TLS definition in /lib/libc.so.6 section .tbss mismatches non-TLS reference in envdir.o

修复很简单将admin/daemontools-0.76/src/error.h中的extern int errno;替换为include <errno.h>之后再重新执行package/install

安装完成之后,会创建/service/command两个目录

/command/
├── envdir -> /usr/local/admin/daemontools/command/envdir
├── envuidgid -> /usr/local/admin/daemontools/command/envuidgid
├── fghack -> /usr/local/admin/daemontools/command/fghack
├── multilog -> /usr/local/admin/daemontools/command/multilog
├── pgrphack -> /usr/local/admin/daemontools/command/pgrphack
├── readproctitle -> /usr/local/admin/daemontools/command/readproctitle
├── setlock -> /usr/local/admin/daemontools/command/setlock
├── setuidgid -> /usr/local/admin/daemontools/command/setuidgid
├── softlimit -> /usr/local/admin/daemontools/command/softlimit
├── supervise -> /usr/local/admin/daemontools/command/supervise
├── svc -> /usr/local/admin/daemontools/command/svc
├── svok -> /usr/local/admin/daemontools/command/svok
├── svscan -> /usr/local/admin/daemontools/command/svscan
├── svscanboot -> /usr/local/admin/daemontools/command/svscanboot
├── svstat -> /usr/local/admin/daemontools/command/svstat
├── tai64n -> /usr/local/admin/daemontools/command/tai64n
└── tai64nlocal -> /usr/local/admin/daemontools/command/tai64nlocal

启动daemontools

启动svscanboot

# /command/svscanboot &

也可以设置开机启动,具体参考:

How to start daemontools

启动之后,查看进程,可以发现svscan做为svscanboot的子进程在运行

# ps -ef | grep sv
root      7547  5172  0 20:44 pts/0    00:00:00 /bin/sh /command/svscanboot
root      7549  7547  0 20:44 pts/0    00:00:00 svscan /service

使用

制作并测试你的脚本

这步的主要目的就是在尝试把程序变为常驻内存进程之前,先确保它可以作为前台程序正常工作。如果脚本有误在前台都不能正常运行,那变为常驻程序后观察结果肯定是达不到预期的。比如我们制作了一个下面这样的PHP脚本

cat /data1/www/test/php/process/hello.php
#!/usr/bin/env php
<?php

while (1) {
    printf("hello, %d\n", $i++);
    sleep(1);
}

构建 daemontools Service

我们把所有的服务统一放到/scratch/service(临时服务)目录下,服务的名称是任意的,比如我们称为hello

创建 /scratch/service 目录

# mkdir -p /scratch/service
# cd /scratch/service/
# mkdir hello
# cd hello

创建一个run文件,其中包含:

#!/bin/sh
exec 2>&1
exec su - root -c "php /data1/www/test/php/process/hello.php" 1>> /data1/www/test/php/process/hello.log

赋予执行权限

# chmod u+x run

安装hello服务并实际开始运行它

# ln -s /scratch/service/hello/ /service/hello

注意: 上面的命令执行后正常情况下服务就已经开始运行了

打开另一个终端,验证程序是否正在运行

$ tail -f /data1/www/test/php/process/hello.log
hello, 67
hello, 68
hello, 69
hello, 70

如果tail命令未产生输出,请执行svc -u hello,虽然前面的命令如/command/svscanboot &已经打开了服务,以防万一由于某种原因关闭了服务,而这本不应该被关闭。

# pstree -a -p 7547
svscanboot,7547 /command/svscanboot
  ├─readproctitle,7550 service errors:...
  └─svscan,7549 /service
      └─supervise,7578 hello
          └─su,7579 - root -c php\040/data1/www/test/php/process/hello.php
              └─php,7580 /data1/www/test/php/process/hello.php

我们使用pstree命令查看进程树,可以看到supervise作为svscan的子进程在运行,su作为supervise的子进程在运行,最终执行的php /data1/www/test/php/process/hello.php又作为su的子进程在运行。

操作和监视你的服务

  • 操作服务

你可以使用svc命令来操作你的服务,可以使用svstat命令来监视你的服务。

svc命令通过向守护进程发送信号来对其进行操作。该命令以root身份在/service目录中执行。例如:

# svc -d hello

上面的命令意思是关闭(down)或停止守护程序。下表是svc参数,它们的含义和信号的表:

ArgActionSignal
-uStart (up)-
-dStop (down)TERM, then CONT
-tRestart if runningTERM

有关该命令的其它参数,请参见: http://cr.yp.to/daemontools/svc.html

  • 监控服务
# svstat hello
hello: up (pid 7917) 5 seconds

svstat可以告诉我们以下信息

  1. 服务目录的名称(hello)
  2. 当前状态(up or down)
  3. 进程PID
  4. 处于当前状态的秒数

Daemontools 架构模型

daemontools

上图的“Connector A: Service”实际上是服务的run脚本,或者是任何二进制可执行文件,shell脚本或PHP/Python/Perl/Ruby/ Lua脚本,它们都是通过exec命令为其分配了一个PID的运行脚本。

通过上图可以知道daemontools的工作方式,计算机首先初始化,然后系统引导运行/command/svscanboot,接着/command/svscanboot依次引导运行/command/readproctitle/command/svscan/command/readproctitle只是一个可以运行的调试工具。

svscan /service是daemontools的一个最重要的机制,每五秒钟它会扫描/service目录(假设/ service是传递给svscan的命令行参数)以查找符号链接目录,并运行该符号连接目录中的run命令(如果尚未运行)

svscan程序会永远一直循环,每次循环做两件事:

1)扫描/service查找所有符号链接目录

2)对于查找到的每个符号链接目录,如果该目录还未运行supervise程序,则在该目录上运行supervise程序

supervise程序它基于我们监控的服务目录(如/service/hello)中的 supervise 目录树中的内容来运行和停止运行run脚本

tree /service/hello/supervise/
/service/hello/supervise/
├── control
├── lock
├── ok
└── status

基本故障排查

对daemontools问题进行故障排除的第一步就是找出正在运行的东西和没有运行的东西,可以参照架构模型将范围一步步缩小。

下面几个ps命令,用于查看正在运行的和未运行的进程:

  • ps -ef | grep sv,查看svscanbootsvscan是否正常在运行

  • # ps -ef | grep supervise,查看supervise是否在正常运行

  • ps ax | grep myprogram,查看你run命令中执行的程序是否在运行,例如,在前面的救命中,它是 hello.php

正确的卸载服务

卸载服务指的不仅仅是停止它,而是停止使用服务。正确的执行步骤如下:

# cd /service/hello/
# rm -rf /service/hello
# svc -dx .

注意: 是首先cd到服务目录下,再使用绝对路径删除软链接,最后在执行svc -dx .

验证是否卸载成功:

# ps -ef | grep hello

还有一种终极大招来卸载服务,那就是关闭服务并杀死正在运行所有的守护进程和其子进程。

参考资料


文章作者: 张权
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张权 !
评论
  目录