最近阅读了一下erlang的supervisor模块,自己写了一些简单的代码来区分不同重启策略的区别,这里记录一下。
首先为了能够直观的观察,我把代码写成了一个application方便用工具观察。
接着实现一个supervisor模块,部分代码如下:
start_link(_Args) ->
io:format("example_sup start_link ~n"),
{ok, Sup} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
{ok, Sup}.
init(_Args) ->
io:format("example_sup init~n"),
{ok, {{one_for_one, 3, 10},
[{temporary1,
{example_child, start, [temporary1]},
temporary,
1000,
worker,
[example_child]},
{temporary2,
{example_child, start, [temporary2]},
temporary,
1000,
worker,
[example_child]},
{transient1,
{example_child, start, [transient1]},
transient,
1000,
worker,
[example_child]},
{transient2,
{example_child, start, [transient2]},
transient,
1000,
worker,
[example_child]},
{permanent1,
{example_child, start, [permanent1]},
permanent,
1000,
worker,
[example_child]},
{permanent2,
{example_child, start, [permanent2]},
permanent,
1000,
worker,
[example_child]}
]
}}.
init函数中按照格式定义了六种子进程规范,但是调用的是同一个回调模块,我只是为了理解重启方式,没必要每个规范都单独定义一个回调模块。
之所以写了六个子进程规范是因为三种重启方式,正常和异常终止两种情况一共需要六个进程一次就可以测试对比出来一种重启策略。
六个进程以启动方式分为三组:temporary,permanent,transient,每组中的两个进程分别分别以后缀1,2来表示,也就是temporary1, temporary2这样,对于后缀为1的进程进行正常终止操作,对于后缀为2的进程进行异常终止操作,操作方法调用example_child:kill(RegName, Reason),RegName就是上面说的temporary1,2等这些进程注册名,Reason正常终止为normal,异常的随便
来看one_for_one重启策略:
我们调用example_app:start()启动该测试应用,我在每个模块的启动函数中都加了打印,子进程调用的同一个模块,所以我把子进程规范中的子进程标识符作为参数传递进入启动函数中,这样就知道哪个子进程启动了。
1> example_app:start().
example_sup start_link
example_sup init
temporary1 init
temporary2 init
transient1 init
transient2 init
permanent1 init
permanent2 init
app start application
ok
正如文档中所说,子进程的启动顺序是列表中从左到右,可以看到打印是先启动了example_sup监督进程,然后回调example_sup:init函数,之后按照从左到右的顺序依次启动了六个进程
我们打开observer工具,如下图,图中进程监控树中进程的顺序不代表它的启动顺序:
接下来,我们分别对三组进程中的两个进程进行正常和异常两种终止方式,然后看结果对比
首先对于temporary1进行正常终止:
example_child:kill(temporary1, normal).
kill temporary1/<0.129.0>
[temporary1] handle_cast {exit, normal}
ok
[temporary1] terminate normal可见temporary启动方式正常终止不会重启,因为子进程启动时会打印init语句
再对temporary2进行异常终止:
example_child:kill(temporary1, normal).
kill temporary1/<0.129.0>
[temporary1] handle_cast {exit, normal}
ok
[temporary1] terminate normal
20180423 修改
这里贴的结果有误,复制错了,后面有空了在修改
还有一堆错误报告就不列出来了,发现temporary启动方式异常终止也不会重启再来看permanent启动方式:
example_child:kill(permanent1, normal).
kill permanent1/<0.133.0>
[permanent1] handle_cast {exit, normal}
ok
[permanent1] terminate normal
permanent1 init
example_child:kill(permanent2, unnormal).
kill permanent2/<0.134.0>
ok
[permanent2] handle_cast {exit, unnormal}
[permanent2] terminate unnormal
permanent2 init
正如文档所说,permanent启动方式,子进程总是被重启再看transient方式:
example_child:kill(transient1, normal).
kill transient1/<0.131.0>
ok
[transient1] handle_cast {exit, normal}
[transient1] terminate normal
example_child:kill(transient2, unnormal).
kill transient2/<0.132.0>
ok
[transient2] handle_cast {exit, unnormal}
[transient2] terminate unnormal
transient2 init
可以看出transient启动方式子进程只有在非正常情况下终止才会被重启,正常终止时是不会重启的以上所说都是在one_for_one重启策略下的,在one_for_all和rest_one_for_one中可以按照上述方法测试,就可以看出哪些进程会被重启
至于simple_one_for_one和one_for_one的区别就是子进程规范只能有一个,子进程是动态添加,我们来修改下example_sup的代码,直接将one_for_one改为simple_one_for_one,直接重启这个application会发现会报错,因为simple_one_for_one策略子进程规范只能有一个,我们试试将子进程规范设置为空列表,启动发现也会报错,我们把其他的都注释掉,只留一个子进程规范启动发现成功了:
example_app:start().
example_sup start_link
example_sup init
app start application
ok我们发现没有子进程,因为子进程需要动态添加,源码在附件中
文中代码附件