supervisor也是Erlang/OTP里一个常用的behavior,用于构建supervisor tree实现进程监控,故障恢复。
而RabbitMQ实现了一个supervisor2,我们从源码角度分析二者的实现和区别。
先介绍一些supervisor的基本概念,假设node_manager_sup是一个supervisor,它的init函数会定义supervisor的一些参数和它的children。
参数:
1. Restart Strategy:
Strategy必须是simple_one_for_one,one_for_one, one_for_all, rest_for_one 中的一种
simple_one_for_one是指supervisor启动时并不启动children,children个数不限,但只能是同一个类型的child,共享一份代码
one_for_one是指supervisor启动时就启动所有children,一旦一个child挂了,supervisor只去重启这一个child process,不影响其他children
one_for_all是指supervisor启动时就启动所有children,一旦一个child挂了,supervisor重启所有children processes
one_for_rest是指supervisor启动时就启动所有children,一旦一个child挂了,supervisor重启在这个child之后声明的所有children
2. intensity and period:如果在period时间内重启超过intensity 次,supervisor就把所有children连同自己一起kill掉
Children的参数:
1. StartFun,child启动函数,必须返回{ok,ChildPid} or
{ok,ChildPid,Info}
2. Restart, 本child的重启策略, permanent代表一直重启,temporary代表绝不重启,transient代表只有当error退出时才重启
3. ShutDown, 本child process的shutdown策略,brutal_kill代表立刻强行kill掉, 正整数代表timeout,即发送一个kill的request,如果timeout时间还没收到response,就强行kill掉,infinity代表只是发送一个kill的request过去,不强行kill,一般用于当child也是一个supervisor的时候。
supervisor也是一个gen_server
supervisor2并没有使用gen_server2而是使用了原版的gen_server
既然是gen_server,supervisor的入口start_link,其实也就是内部的init函数:
1. 检查supervisor的所有参数
2. 如果不是simple_one_for_one, 就启动所有children。
当有child process exit 时,根据gen_server的behavior,会由handle_info({‘EXIT‘, Pid, Reason}, State)来处理,根据此child的Restart类型进行重启。
重启之前会更新重启次数,如果发现在period时间内重启超过intensity 次,就把所有children连同自己一起kill掉
如果重启失败会再次重启,再次重启的请求由handle_cast处理
最后再看supervisor的几个export函数:
start_child, 是一个gen_server:call,
1. 如果是simple_one_for_one,就从children里随便拿一个启动,因为simple_one_for_one的children都一样,而且都没随supervisor启动
2. 如果不是simple_one_for_one,start_child就传入一个child,启动这个child
restart_child,delete_child, terminate_child,which_children,which_children也都是gen_server:call
supervisor2:
supervisor2并没有使用RabbitMQ自己的gen_server2而是使用gen_server,原因supervisor接收的request比较少(都是重启,启动,终止之类),也没有用到hibernate。
supervisor2对supervisor的改动并不算多:
1. intrinsic, child的Restart增加了intrinsic类型,和transient很像,不同的是,transient如果child非正常退出,就将其删除,supervisor照常运行;但intrinsic如果child非正常退出,supervisor也会退出并删除其他所有children。
2. Delay, 如上所述,在period时间内重启超过intensity 次,supervisor就把所有children连同自己一起kill掉。supervisor2里,child的Restart可以写{permanent, Delay} | {transient, Delay} | {intrinsic, Delay},这样在period时间内重启超过intensity 次后,supervisor并不会kill所有,而是等待Delay时间再尝试重启该child。