【概况】
supervisor behaviour 是用来实现监控其他子进程的 supervisor 进程的模块;
子进程可以是另一个 supervisor 进程,也可以是一个 worker 进程;
worker 进程一般使用 gen_event,gen_fsm 或 gen_server behaviour 来实现 ;
一个使用 supervisor behaviour 实现的 supervisor 有一个接口方法的标准集,包括跟踪和错误报告的功能;
supervisor 机制用来构建一个分层进程结构,称为 supervision tree,这是组织一个容错系统的好方式。
【原则】
supervisor 负责启动、停止和监控它的子进程;
supervisor 在必要时,通过重启它的子进程来使其保持活着;
supervisor 的子进程由称作子进程规范的列表进行定义;
当 supervisor 启动时,子进程按子进程规范列表中内容从左至右的顺序启动;
当 supervisor 终止时,会按子进程启动顺序的反顺序终止相应子进程。
【重启策略】
one_for_one
如果一个子进程停止,则只重启该子进程;
one_for_all
如果一个子进程停止,则所有其他子进程也停止,然后所有子进程重启;
rest_for_one
如果一个子进程停止,则启动顺序中在它之后的所有其他子进程也停止,然后停止的这些子进程重启;
simple_one_for_one
一个简化的 one_for_one ,所有的子进程都是同样进程类型并且是动态添加的实例;
【最大重启频率】
supervisor 有一个自带的机制来限制给定时间内重启的次数;
{RestartStrategy, MaxR, MaxT}
如果在最近的 MaxT 秒之内有超过 MaxR 次数的重启,则 supervisor 停止它本身和它所有的子进程;
当 supervisor 停止后,下一个更高级别的 supervisor 将进行下一步动作,重启上述停止的 supervisor 或者终止本身;
重启机制的意图是防止一个进程由于某些原因重复性的死掉;
【子规范】
{Id, StartFunc, Restart, Shutdown, Type, Modules}
Id 用于 supervisor 内部识别子规范的名字;
StartFunc 定义了用来启动子进程的的方法,由三元组 {M, F, A} 来确定,其一般情况下调用的是 supervisor:start_link 、 gen_server:start_link 、 gen_fsm:start_link 或 gen_event:start_link ,或对上述函数的相应封装。
Restart 定义了子进程什么时候重启:
- permanent 表示子进程始终重启;
- temporary 表示子进程决不重启;
- transient 表示只有在子进程异常终止时才重启,即除了 normal 以外的终止原因;
Shutdown 定义了子进程怎样终止:
- brutal_kill 表示子进程使用 exit(Child, kill) 来无条件的终止;
- 一个整数 timeout 值表示 supervisor 通过调用 exit(Child, shutdown) 告诉子进程请终止自己,然后等待子进程返回退出信号;如果没有在指定的时间内接收到(来自子进程的) 退出信号,则使用 exit(Child, kill) 无条件终止子进程;
- 如果子进程是另一个 supervisor,则应该设置为 infinity 来给子树足够的时间来终止;
Type 指定子进程是一个 supervisor 还是一个 worker;
Modules 应该是一个 list,含有一个元素 [Module]。
如果子进程是一个 supervisor,或者子进程是 woker 且采用 gen_server 或 gen_fsm 行为模式实现,则 Modules 是 callback 模块的名字;
如果子进程是 worker 且采用 gen_event 行为模式实现,则 Modules 应该为 dynamic ,该信息用来在升级和降级时供 release handler 使用;
【参数选择原则】
按照子进程规范启动的子进程,若是要求该子进程为任何时候均可访问的注册进程,则一般使用参数 permanent ;
按照子进程规范启动的子进程,若是要求该子进程终止之前不需要做任何清理工作,则不需要设置 timeout 值,可以设置为 brutal_kill;
若是要求其终止之前做相应资源清理,则需要设置 timeout 超时值。
【启动 supervisor】
supervisor:start_link(CallbackModuleName, InitCallbackParamList).
启动一个新的 supervisor 进程并链接到 OTP 系统的监督树中;
- 第一个参数 callback 模块的名字,是 init callback 方法所在 module 的名字;
- 第二个参数 init callback 方法的参数列表;
supervisor 的执行流程如下:
supervisor:start_link --> init --> 根据指定的子规范的入口来启动它的所有子进程
注意: supervisor:start_link 是同步的,当所有子进程启动之后才会返回 。
【动态添加子进程】
supervisor:start_child(Sup, ChildSpec)
Sup 是 supervisor 的 pid 或名字,ChildSpec 是子进程规范;
使用 supervisor:start_child/2 添加的子进程会表现出像其他子进程一样的行为,除了这点,如果 supervisor 死掉然后重启,则所有动态添加的子进程都将丢失;
【停止一个子进程】
任何子进程,不管静态的还是动态的,都可以使用 shutdown 规范来停止。
supervisor:terminate_child(Sup, Id)
停止的子进程的子规范使用如下调用来删除。
supervisor:delete_child(Sup, Id)
Sup 是 supervisor 的 pid 或 name,Id 是子规范里指定的 id 。
就像动态添加的子进程一样,如果 supervisor 本身重启,那么删除静态子进程的效果会丢失。
【simple_one_for_one】
采用 simple_one_for_one 重启策略的 supervisor 是一个简化的 one_for_one supervisor,所有的子进程都是动态添加的同一类型子进程的实例。
-module(simple_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link(simple_sup, []).
init(_Args) ->
{ok, {{simple_one_for_one, 0, 1},
[{call, {call, start_link, []},
temporary, brutal_kill, worker, [call]}]}}.
当 supervisor 通过 supervisor:start_link/2 启动后,将不会启动任何子进程,而是通过调用如下代码来动态添加子进程:
supervisor:start_child(Sup, List)
Sup 是 supervisor 的 pid 或 name,List 是一个任意的 term 列表,该列表值将会被动态添加到子规范的参数列表里;
如果启动方法指定为 {M, F, A},则子进程的启动是通过调用 apply(M, F, A++List) 来完成的。
【supervisor 的终止】
既然 supervisor 是 supervision tree 的一部分,则其将自动被它自身的 supervisor 所终止;当终止时,它会按启动的反顺序根据相应的 shutdown 规范来自动终止它所有的子进程,然后终止本身。