我们写的程序不可能都没有 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 由两部份组成:
- 外部程序: heart
- erlang port 模块: heart.erl
heart 机制有两个用处:
- erlang 虽然内置了很多 supervisor,可以保证 process 的高可靠性,但是假如 emulator 死亡了,那这一切都消失了,erlang 只能靠 heart 来重新启动。
- 热部署的时候 release_handling 需要重新启动 emulator 的时候也必须借助外部程序,即 heart 来辅助。
祝玩得开心!
-=-=-=-=- 我是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 作为子进程;而我之前那篇文章中的实现是倒过来的~~