【原创】Erlang 之 heart 功能


在霸爷博文《 Erlang heart – 高可靠性的最后防线》中写到

-=-=-=-=- 我是88界奥斯卡颁奖礼的分隔线 -=-=-=-=-

      我们写的程序不可能都没有 bug ,都存在 crash 的危险。很多时候我们需要个看门狗(watchdog)程序,在发现系统不正常的时候,就把系统重新启动。这类 watchdog 程序从内核到各种高可用程序都会设置有一个。erlang 系统当然不能免俗,也有个 heart 。

我们来看下流程和效果:
$ export HEART_COMMAND="erl -heart"
$ erl -heart
heart_beat_kill_pid = 12640
Erlang R15B03 (erts-5.9.3.1) 1 [64-bit] [smp:16:16] [async-threads:0] [hipe] [kernel-poll:false]
 
Eshell V5.9.3.1  (abort with ^G)
1> os:getpid().
"12640"
2>
CTRL + Z 挂起 erlang
$ pstree -p
…
+-beam.smp(12640)-+-heart(12670)
| | | |-{beam.smp}(12647)
| | | |-{beam.smp}(12648)
| | | |-{beam.smp}(12650)
| | | |-{beam.smp}(12653)
| | | |-{beam.smp}(12654)
| | | |-{beam.smp}(12655)
| | | |-{beam.smp}(12656)
| | | |-{beam.smp}(12657)
| | | |-{beam.smp}(12658)
| | | |-{beam.smp}(12659)
| | | |-{beam.smp}(12660)
| | | |-{beam.smp}(12661)
| | | |-{beam.smp}(12662)
| | | |-{beam.smp}(12663)
| | | |-{beam.smp}(12664)
| | | |-{beam.smp}(12665)
| | | |-{beam.smp}(12666)
| | | |-{beam.smp}(12667)
| | | |-{beam.smp}(12668)
| | | `-{beam.smp}(12669)
| | `-pstree(13702)
…

$ heart: Fri Aug 23 20:36:25 2013: heart-beat time-out, no activity for 65 seconds
heart_beat_kill_pid = 27920
      我们看到 erl 重新被启动起来了。现在简单的分析下原理。

heart 由两部份组成
  1. 外部程序: heart
  2. erlang port 模块: heart.erl
      当开启 heart 的时候(erl –heart xxx) 外部程序 heart 被 erlang 模块 heart.erl 作为独立的(子)进程启动起来,监视 emulator 的运作。heart.erl 每隔一定的时间向 heart 外部程序报告状态。当外部 heart 没有监测到心跳的时候就要采取行动,重新运行 $HEART_COMMAND 所指定的命令。

heart 机制有两个用处
  1. erlang 虽然内置了很多 supervisor,可以保证 process 的高可靠性,但是假如 emulator 死亡了,那这一切都消失了,erlang 只能靠 heart 来重新启动。
  2. 热部署的时候 release_handling 需要重新启动 emulator 的时候也必须借助外部程序,即 heart 来辅助。
所以在 heart 模式下,你的 erlang 程序是杀不掉的,除非你先 kill 掉 heart 进程,这个 pid 就是启动的时候输出的 heart_beat_kill_pid (从下面我的实验中可以看出,霸爷的这个结论是错误的!)。

祝玩得开心!

-=-=-=-=- 我是88界奥斯卡颁奖礼的分隔线 -=-=-=-=-

基于上述内容, 实验验证如下

【验证博文中的操作】
[root@Betty coredump_test]# export HEART_COMMAND="erl -heart"     -- 执行 heart 重启业务时执行的命令
[root@Betty coredump_test]# erl -heart                            -- 启动 heart 功能
heart_beat_kill_pid = 4904
Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V6.0  (abort with ^G)
1> 
1> os:getpid().
"4904"
2> 
[1]+  Stopped                 erl -heart              -- 这里通过 Ctrl + Z 挂起
[root@Betty coredump_test]# 
[root@Betty coredump_test]# 
[root@Betty coredump_test]# heart: Mon Mar  7 15:09:49 2016: heart-beat time-out, no activity for 65 seconds   -- 65 秒后输出该打印信息
heart_beat_kill_pid = 5066

[1]+  已杀死               erl -heart
[root@Betty coredump_test]#
另一个窗口
[root@Betty ~]# ps ajxf
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 ...
 1890  3925  3925  3925 ?           -1 Ss       0   0:00  \_ sshd: root@pts/1 
 3925  3927  3927  3927 pts/1     4904 Ss       0   0:00      \_ -bash
 3927  4904  4904  3927 pts/1     4904 Sl+      0   0:00          \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /us
 4904  4926  4926  4926 ?           -1 Ss       0   0:00              \_ heart -pid 4904
...

[root@Betty ~]# pstree -p
init(1)─┬─abrtd(2369)
        ...
        ├─sshd(1890)─┬─sshd(3465)─┬─bash(4576)
        │            │            └─bash(14788)───pstree(5058)
        │            └─sshd(3925)───bash(3927)───beam.smp(4904)─┬─heart(4926)
        │                                                       ├─{beam.smp}(4908)
        │                                                       ├─{beam.smp}(4909)
        │                                                       ├─{beam.smp}(4910)
        │                                                       ├─{beam.smp}(4911)
        │                                                       ├─{beam.smp}(4912)
        │                                                       ├─{beam.smp}(4913)
        │                                                       ├─{beam.smp}(4914)
        │                                                       ├─{beam.smp}(4915)
        │                                                       ├─{beam.smp}(4916)
        │                                                       ├─{beam.smp}(4917)
        │                                                       ├─{beam.smp}(4918)
        │                                                       ├─{beam.smp}(4919)
        │                                                       ├─{beam.smp}(4920)
        │                                                       ├─{beam.smp}(4921)
        │                                                       ├─{beam.smp}(4922)
        │                                                       ├─{beam.smp}(4923)
        │                                                       ├─{beam.smp}(4924)
        │                                                       └─{beam.smp}(4925)
        ...
[root@Betty ~]# ps ajxf|grep heart
14788  5061  5060 14788 pts/3     5060 S+       0   0:00  |   |   \_ grep heart
 3927  4904  4904  3927 pts/1     3927 Tl       0   0:00          \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 4904  4926  4926  4926 ?           -1 Ss       0   0:00              \_ heart -pid 4904
[root@Betty ~]# 
[root@Betty ~]# ps ajxf|grep heart         -- 65 秒后输出内容发生了变化
14788  5096  5095 14788 pts/3     5095 S+       0   0:00  |   |   \_ grep heart
    1  4926  4926  4926 ?           -1 Ss       0   0:00 heart -pid 4904
 4926  5066  4926  4926 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5066  5088  5088  5088 ?           -1 Ss       0   0:00      \_ heart -pid 5066
[root@Betty ~]#

【验证手动杀死各类进程的效果】

后台启动 erlang 进程
[root@Betty ~]# erl -heart -detached
可以看到,此时创建了两个进程:erlang 业务进程 + heart 子进程
[root@Betty ~]# ps ajxf|grep heart|grep -v grep
    1  5505  5504  5504 ?           -1 Sl       0   0:00 /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart -noshell -noinput
 5505  5524  5524  5524 ?           -1 Ss       0   0:00  \_ heart -pid 5505
[root@Betty ~]#
杀死 erlang 业务进程后,能看到其 heart 子进程会重新创建 erlang 业务进程和新 heart 子进程。
[root@Betty ~]# kill -9 5505
[root@Betty ~]# ps ajxf|grep heart|grep -v grep
    1  5524  5524  5524 ?           -1 Ss       0   0:00 heart -pid 5505
 5524  5534  5524  5524 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5534  5556  5556  5556 ?           -1 Ss       0   0:00      \_ heart -pid 5534
[root@Betty ~]#
再一次杀死 erlang 业务进程,可以看到 erlang 进程的父 heart 进程也消失了,而 erlang 进程的 heart 子进程会重新启动 erlang 业务进程和其 heart 子进程。
[root@Betty ~]# kill -9 5534
[root@Betty ~]# 
[root@Betty ~]# ps ajxf|grep heart|grep -v grep
    1  5556  5556  5556 ?           -1 Ss       0   0:00 heart -pid 5534
 5556  5568  5556  5556 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5568  5590  5590  5590 ?           -1 Ss       0   0:00      \_ heart -pid 5568
[root@Betty ~]#
杀死 erlang 业务进程的父 heart 进程,可以看到对 erlang 业务进程和其子 heart 进程无任何影响。
[root@Betty ~]# 
[root@Betty ~]# kill -9 5556
[root@Betty ~]# ps ajxf|grep heart|grep -v grep
    1  5568  5556  5556 ?           -1 Sl       0   0:00 /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5568  5590  5590  5590 ?           -1 Ss       0   0:00  \_ heart -pid 5568
[root@Betty ~]#
再一次杀死 erlang 业务进程,能看到其 heart 子进程会重新创建 erlang 业务进程和新 heart 子进程。
[root@Betty ~]# 
[root@Betty ~]# kill -9 5568
[root@Betty ~]# ps ajxf|grep heart|grep -v grep
    1  5590  5590  5590 ?           -1 Ss       0   0:00 heart -pid 5568
 5590  5605  5590  5590 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5605  5627  5627  5627 ?           -1 Ss       0   0:00      \_ heart -pid 5605
[root@Betty ~]#
杀死 erlang 业务进程的 heart 子进程,可以看到 erlang 业务进程也随之消失了。
[root@Betty ~]# 
[root@Betty ~]# kill -9 5627
[root@Betty ~]# ps ajxf|grep heart|grep -v grep
[root@Betty ~]# 

【验证通过不同信号杀死进程的效果】

以下内容均在窗口 1 中观察
初次启动
[root@Betty ~]# erl -heart
heart_beat_kill_pid = 5668
Erlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V6.0  (abort with ^G)
1> 

(此时在窗口 2 中 kill -9 xxx)

1>
1> 已杀死
heart: Mon Mar  7 15:48:42 2016: Erlang has closed.
heart_beat_kill_pid = 5697

[root@Betty ~]# 

(此时在窗口 2 中再次 kill -9 xxx )

此时能看到如下一行信息输出,这是由创建 erlang 业务进程的 heart 父进程输出的,下同。
[root@Betty ~]# heart: Mon Mar  7 15:49:05 2016: Executed "erl -heart" -> 9. Terminating.
heart: Mon Mar  7 15:49:05 2016: Erlang has closed.
heart_beat_kill_pid = 5725

[root@Betty ~]# 
[root@Betty ~]# 
[root@Betty ~]# heart: Mon Mar  7 15:49:23 2016: Executed "erl -heart" -> 9. Terminating.
heart: Mon Mar  7 15:49:23 2016: Erlang has closed.
heart_beat_kill_pid = 5754

[root@Betty ~]# 

(此时在窗口 2 中 kill -15 xxx )

[root@Betty ~]# heart: Mon Mar  7 15:49:56 2016: Executed "erl -heart" -> 15. Terminating.
heart: Mon Mar  7 15:49:56 2016: Erlang has closed.
heart_beat_kill_pid = 5783

[root@Betty ~]# 

(此时在窗口 2 中 kill -SIGUSR1 xxx )

此时能看到,有生成 crash dump 文件的信息,并最后通过 -9 信号杀死 erlang 业务进程(没有找到生成的 crash dump 文件)。
[root@Betty ~]# heart: Mon Mar  7 15:50:42 2016: Erlang is crashing .. (waiting for crash dump file)
heart: Mon Mar  7 15:50:42 2016: Executed "erl -heart" -> 9. Terminating.
heart_beat_kill_pid = 5816

[root@Betty ~]# 

再来一次,仍旧没找到 crash dump 文件
[root@Betty ~]# 
[root@Betty ~]# heart: Mon Mar  7 15:52:58 2016: Erlang is crashing .. (waiting for crash dump file)
heart: Mon Mar  7 15:52:58 2016: Executed "erl -heart" -> 9. Terminating.
heart_beat_kill_pid = 5851

[root@Betty ~]# 

(此时在窗口 2 中 kill -SIGUSR2 xxx )

[root@Betty ~]# heart: Mon Mar  7 15:54:17 2016: Executed "erl -heart" -> 12. Terminating.
heart: Mon Mar  7 15:54:17 2016: Erlang has closed.
heart_beat_kill_pid = 5884

[root@Betty ~]# 

(此时在窗口 2 中 kill -SIGSEGV xxx )


可以看到,此时在当前目录生成了操作系统的 core 文件。
[root@Betty ~]# heart: Mon Mar  7 15:55:26 2016: Executed "erl -heart" -> 139. Terminating.
heart: Mon Mar  7 15:55:26 2016: Erlang has closed.
heart_beat_kill_pid = 5915

[root@Betty ~]# 
[root@Betty ~]# ll
总用量 11132
...
-rw-------   1 root root 107769856 3月   7 15:55 core.5884
...
[root@Betty ~]#

(此时在窗口 2 中 kill -SIGABRT xxx )

可以看到,此时在当前目录同样生成了操作系统的 core 文件。
[root@Betty ~]# heart: Mon Mar  7 15:56:59 2016: Executed "erl -heart" -> 134. Terminating.
heart: Mon Mar  7 15:56:59 2016: Erlang has closed.
heart_beat_kill_pid = 5949

[root@Betty ~]# 
[root@Betty ~]# ll
总用量 21880
...
-rw-------   1 root root 107769856 3月   7 15:55 core.5884
-rw-------   1 root root 107769856 3月   7 15:56 core.5915
...
[root@Betty ~]# 

(此时在窗口 2 中 killall heart)

此时可以看到生成了 erl_crash.dump 文件
[root@Betty ~]# 
Crash dump was written to: erl_crash.dump
Kernel pid terminated (heart) ()

[root@Betty ~]# 
[root@Betty ~]# ll
总用量 780
..
-rw-r-----   1 root root 396922 3月   7 16:10 erl_crash.dump
...
以下内容均在窗口 2 中观察
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
 3927  5668  5668  3927 pts/1     5668 Sl+      0   0:00          \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5668  5690  5690  5690 ?           -1 Ss       0   0:00              \_ heart -pid 5668
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
[root@Betty upu]# 
[root@Betty upu]# kill -9 5668
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
    1  5690  5690  5690 ?           -1 Ss       0   0:00 heart -pid 5668
 5690  5697  5690  5690 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5697  5719  5719  5719 ?           -1 Ss       0   0:00      \_ heart -pid 5697
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# kill -9 5697
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
    1  5719  5719  5719 ?           -1 Ss       0   0:00 heart -pid 5697
 5719  5725  5719  5719 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5725  5747  5747  5747 ?           -1 Ss       0   0:00      \_ heart -pid 5725
[root@Betty upu]# 
[root@Betty upu]# kill -9 5725
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
    1  5747  5747  5747 ?           -1 Ss       0   0:00 heart -pid 5725
 5747  5754  5747  5747 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5754  5776  5776  5776 ?           -1 Ss       0   0:00      \_ heart -pid 5754
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# kill -15 5754
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
    1  5776  5776  5776 ?           -1 Ss       0   0:00 heart -pid 5754
 5776  5783  5776  5776 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5783  5805  5805  5805 ?           -1 Ss       0   0:00      \_ heart -pid 5783
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# kill -SIGUSR1 5783
[root@Betty upu]# 
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
    1  5805  5805  5805 ?           -1 Ss       0   0:00 heart -pid 5783
 5805  5816  5805  5805 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5816  5838  5838  5838 ?           -1 Ss       0   0:00      \_ heart -pid 5816
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# kill -SIGUSR1 5816
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
    1  5838  5838  5838 ?           -1 Ss       0   0:00 heart -pid 5816
 5838  5851  5838  5838 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5851  5873  5873  5873 ?           -1 Ss       0   0:00      \_ heart -pid 5851
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# kill -SIGUSR2 5851
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
    1  5873  5873  5873 ?           -1 Ss       0   0:00 heart -pid 5851
 5873  5884  5873  5873 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5884  5906  5906  5906 ?           -1 Ss       0   0:00      \_ heart -pid 5884
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# kill -SIGSEGV 5884
[root@Betty upu]# 
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
    1  5906  5906  5906 ?           -1 Ss       0   0:00 heart -pid 5884
 5906  5915  5906  5906 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5915  5938  5938  5938 ?           -1 Ss       0   0:00      \_ heart -pid 5915
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# kill -SIGABRT 5915
[root@Betty upu]# 
[root@Betty upu]# ps ajxf|grep heart|grep -v grep
    1  5938  5938  5938 ?           -1 Ss       0   0:00 heart -pid 5915
 5938  5949  5938  5938 ?           -1 Sl       0   0:00  \_ /usr/local/lib/erlang/erts-6.0/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -heart
 5949  5971  5971  5971 ?           -1 Ss       0   0:00      \_ heart -pid 5949
[root@Betty upu]# 
[root@Betty upu]# 
[root@Betty upu]# killall heart


补充:
      很久之前写过一篇文章,《【原创】服务器开发之 Daemon 和 Keepalive》,其中的 keepalive 功能和本文中 erlang 的 heart 功能类似,差别在于 erlang 中的实现为,业务作为父进程,heart 作为子进程;而我之前那篇文章中的实现是倒过来的~~


你可能感兴趣的:(erlang,heart)