supervisor
模块
supervisor
模块概述
通用监督行为模式
描述
执行一个监督者的行为模式,一个进程监督其他被称作子进程的进程。子进程可以是另外一个监督者或者一个工作进程。工作进程一般是 gen_event,gen_fsm 或者 gen_server 几种行为模式的一种。使用该模块的监督者会有一套标准的函数接口并且包括追踪报告和错误报告的功能。Supervisors 是用来建立一个被称作监督树的进程层次结构,是构建一个容错应用的好方式。参考OTP设计原则获取更多信息。
一个supervisor采取要被监督的子进程执行的代码是从一个导出预设函数的回调模块中加载的方式。
除非另作说明,否则所有在该模块的函数将会失败如果指定的监督者不存在或者传递了错误的参数。
Supervision 准则
监督者负责启动、停止和监视它的子进程。一个监督者最基本的作用是在必要的适合重启子进程来保证子进程的存在。
一个监督者的子进程通常被定义为一个子进程规范列表。当监督进程启动时,子进程将会以列表中从左到右的顺序启动。当子进程终止时,首先以启动列表的反序即从右到左的顺序来终止它的子进程。
一个监督者可以使用以下的一个重启策略:
one_for_one:如果一个子进程终止了,只有这个子进程被重启,不影响其它子进程。
one_for_all:如果一个子进程终止了,所有的子进程都会被重启。
- rest_for_one:如果一个子进程终止了,在该子进程后面启动的所有子进程以及该子进程都会被重启。
simple_one_for_one:one_for_one的精简形式,不同的是仅适用于当所有的子进程是同一类型并且动态添加到监督树的时候。
delete_child/2和restart_child/2函数不能在使用simple_one_for_one策略的监督树中使用,否则会返回{error,simple_one_for_one}。
terminate_child/2函数可以通过将子进程的pid作为第二个参数在simple_one_for_one策略下的子进程使用。如果改用规范标识符(监控策略中用于监督进程内部识别子进程的标识符),terminate_child/2将会返回{error,simple_one_for_one}。
由于一个使用simple_one_for_one策略的监督者可以有很多子进程,并且同时关闭它们。所以,子进程的关闭顺序是未定义的。基于该原因,可能对关闭策略会有额外的开销。
为了防止监督树陷入重启子进程的死循环,定义了最大重启频率,使用两个数字MaxR和MaxT。如果在MaxT秒内重启次数大于MaxR,该监督进程将会终止所有的子进程然后终止它自己。
以下是子进程规范的类型定义:
child_spec() = {Id, StartFunc, Restart, Shutdown, Type, Modules}
Id = term()
StartFunc = {M, F, A}
M = F = atom()
A = [term()]
Restart = permanent | transient | temporary
Shutdown = brutal_kill | int() > 0 | infinity
Type = worker | supervisor
Modules = [Module] | dynamic
Module = atom()
- Id 是监督者内部用来标识子进程的名字(规范标识符)
StratFunc定义了启动子进程执行的函数。它应该是一个module-function-arguments的元组{M, F, A}以apply(M, F, A)的方式使用。
该启动函数必须创建并连接到子进程,并且应该返回{ok, Child}或者{ok, Child, Info},Child是子进程的pid,Info是一个表示被监督者忽略的term。
启动函数也可以返回ignore如果子进程因为某种原因无法启动,这种情况该子进程标识将会被监督者保留(除非它是一个临时进程)但是不存在的子进程将会被忽略。
如果中间出错,该函数也可能会返回一个错误元组{error, Error}。
注意不同行为模式中的start_link函数都满足以上的要求。
Restart定义了终止的子进程何时应该被重启。permanent表示该子进程总是应该被重启,temporary表示该子进程永远不该被重启(即使重启策略设置的是reset_for_one或者one_for_all并且一个进程的死亡会造成临时进程被终结),transient表示子进程只会在非正常终止的情况被重启,即终止原因不是normal、shutdown或者{shutdown, Term}。
Shutdown 定义了子进程如何被终止。 brutal_kill表示子进程会被exit(Child, kill)强制终止。 超时时间表示监督者会调用exit(Child, shutdown)来告诉子进程要终止它,然后等待子进程发送的shutdown终止信号。如果在指定的时间内没有收到终止信号,该子进程将会被使用exit(Child, kill)强制终止。
如果该子进程是另外一个监督者,Shutdown应该被设置为infinity来给子监督树足够的时间来终止。子进程是工作进程时,也可以设置为infinity。
警告:当子进程是工作进程的时候,要设置Shutdown为infinity一定要谨慎。因为,这种情况,监控树的终止决定于这个子进程的终止,它必须以一种安全的方式执行并且它的清除操作必须总是返回。
注意所有使用标准OTP行为模式的子进程都自动遵循上面的终止协议。
- Type 指定子进程是监督者还是工作进程。
Modules is used by the release handler during code replacement to determine which processes are using a certain module. 一般来说,Modules应该是只有一个元素的列表[Module],如果子进程是一个监督者或者gen_server或者gen_fsm,Module应该是回调模块。如果子进程是动态设置回调模块的事件管理器(gen_event),Modules应该为dynamic。查阅OTP设计原则获取关于发布处理的更多信息。
在内部,监督者还跟踪子进程的pid,如果子进程不存在则是undefined。
数据类型
child() = undefined | pid()
child_id() = term()
Not a pid().
child_spec() =
{ Id :: child_id(),
StartFunc :: mfargs(),
Restart :: restart(),
Shutdown :: shutdown(),
Type :: worker(),
Modules :: modules()}
mfargs() =
{M :: module(), F :: atom(), A :: [term()] | undefined}
modules() = [module()] | dynamic
restart() = permanent | transient | temporary
shutdown() = brutal_kill | timeout()
strategy() = one_for_all
| one_for_one
| rest_for_one
| simple_one_for_one
sup_ref() = (Name :: atom())
| {Name :: atom(), Node :: node()}
| {global, Name :: atom()}
| pid()
worker() = worker | supervisor
导出函数
start_link(Module, Args) -> startlink_ret()
start_link(SupName, Module, Args) -> startlink_ret()
Types:
SupName = sup_name()
Module = module()
Args = term()
startlink_ret() = {ok, pid()}
| ignore
| {error, startlink_err()}
startlink_err() = {already_started, pid()}
| {shutdown, term()}
| term()
sup_name() = {local, Name :: atom()} | {global, Name :: atom()}
创建一个监督进程作为监控树的一部分。该函数就其他方面而言,将会确保监督者和调用它的进程连接起来(它的监督进程)。
创建的监督进程调用Module:init/1获取重启策略、最大重启频率和子进程规范。为了确保启动操作的同步,start_link/2,3直到所有子进程被启动,Module:init/1返回之后才返回。
如果SupName = {local, Name}该监督进程使用register/2在本地注册为Name。如果SupName = {global, Name},该supervisor使用global:register_name/2在全局注册为Name。如果SupName = {via, Moudle, Name},the supervisor is registered as Name using the registry represented by Module。回调模块Module应该导出register_name/2,unregister_name/1和send/2函数,它们应该表现得像全局的函数一样。因此,{via, global, Name}是一个有效的引用。(这一段不是很明白,翻译的很不通顺)。
如果没有提供名字,该监督者不会被注册。
Module是回调模块的名字。
Args是传递给Module:init/1作为参数的term。
如果监督进程和它的子进程全部创建成功(即所有的子进程的start函数返回{ok, Child}, {ok, Child, Info}, 或者ignore)该函数返回{ok, Pid},Pid是监督进程的pid。如果指定的注册名字已经有对应的进程,该函数返回{error, {already_started, Pid}},Pid是那个已有的进程的pid。
如果Module:init/1返回ignore,该函数也返回ignore,该监督进程以normal原因终止。如果Module:init/1失败了或者返回一个不正确的值,该进程返回{error, Term}, Term是一个关于错误信息的term,该监督进程会以Term原因终止。
如果任何一个子进程start函数失败或者返回一个错误元组或者一个错误的值,该supervisor会首先以shutdown原因终止已经启动的子进程然后终止它自身并返回{error, {shutdown, Reason}}。
start_child(SupRef, ChildSpec) -> startchild_ret()
Types:
SupRef = sup_ref()
ChildSpec = child_spec() | (List :: [term()])
child_spec() =
{ Id :: child_id(),
StartFunc :: mfargs(),
Restart :: restart(),
Shutdown :: shutdown(),
Type :: worker(),
Modules :: modules()}
startchild_ret() = {ok, Child :: child()}
| {ok, Child :: child(), Info :: term()}
| {error, startchild_err()}
startchild_err() = already_present
| {already_started, Child :: child()}
| term()
在监督进程SupRef下动态添加一个对应的子进程。
SupRef可以是:
pid
Name,如果该监督者是本地注册的
{Name, Node},如果该监督者在另一个节点本地注册的
{global, Nmae},如果该监督者是全局注册的
{via, Module, Name},如果该监督者通过进程注册表注册的
ChildSpec应该是一个有效的子进程规范(除非该监督进程使用simple_one_for_one重启策略,见下文)。子进程将会使用start函数以子进程规范中指定的方式启动。
如果是simple_one_for_one的情况,在Module:init/1中定义的子进程规范将会被使用并且ChildSpec也应该是一个任意的terms列表。子进程将会通过在已经存在的start函数参数中追加列表的方式启动,即通过调用apple(M, F, A++List),{M, F, A}是子进程规范定义的start函数。
如果已经存在一个指定Id的子进程规范,ChildSpec被丢弃该函数返回{error, already_present} 或者{error, {already_started, Child}},取决于对应的子进程是否在运行。
如果子进程start函数返回{ok, Child}或者{ok, Child, Info},子进程规范和pid被添加到监督进程然后该函数返回同样的值。
如果子进程start函数返回ignore,子进程规范被添加到监督进程,pid被设置为undefined,该函数返回{ok, undefined}。
如果子进程start函数返回一个错误元组或者一个错误的值,或者它执行失败了,该子进程说明被丢弃并且该函数返回{error, Error},Error是一个包含子进程说明和错误信息的term。
terminate_child(SupRef, Id) -> Result
Types:
SupRef = sup_ref()
Id = pid() | child_id()
Result = ok | {error, Error}
Error = not_found | simple_one_for_one
告诉监督进程SupRef终止指定的子进程。
如果supervisor不是simple_one_for_one,Id必须是子进程的规范标识符。该子进程如果存在将被监督进程终止,除非它是一个临时进程,否则他的进程标志符会被监督进程保留。该子进程可能稍后会被监督进程重启。该子进程还可以通过显示的调用restart_child/2来重启。使用delete_child/2可以移除子进程标识符。
如果该子进程是一个临时进程,该进程标识符将会在其终止的时候被删除。这意味着delete_child/2对于临时进程是没有意义的,restart_child/2也不能用于这些临时子进程。
如果一个监督进程采用simple_one_for_one,Id必须是该子进程的pid。如果指定的进程存活着但不是该监督进程的子进程,该函数将会返回{error, not_found}。如果该子进程标识符不是一个pid,该函数将会返回{error, simple_one_for_one}。
如果成功,该函数返回ok。如果没有给定Id的子进程规范,该函数返回{error, not_found}。
查看start_child/2获取关于SupRef的描述。
delete_child(SupRef, Id) -> Result
Types:
SupRef = sup_ref()
Id = child_id()
Result = ok | {error, Error}
Error = running | restarting | not_found | simple_one_for_one
告诉监督进程SupRef删除进程id为Id的子进程标识符。对应的子进程必须保证不在运行状态,使用terminate_child/2来终止它。
查看start_child/2获取关于SupRef的描述。
如果成功,该函数返回ok。如果对应的进程标识符存在但是对应的进程在运行状态或者将被重启,该函数各自返回{error, running}或者{error, restarting}。如果对应的进程标识符不存在,该函数返回{error, not_found}。
restart_child(SupRef, Id) -> Result
Types:
SupRef = sup_ref()
Id = child_id()
Result = {ok, Child :: child()}
| {ok, Child :: child(), Info :: term()}
| {error, Error}
Error = running
| restarting
| not_found
| simple_one_for_one
| term()
告诉监督进程SupRef重启一个Id对应的子进程标识符的子进程。该子进程标识符必须存在并且对应的进程不能处于运行状态。
注意对于临时子进程,子进程标识符在子进程终止时会自动删除,因此不可能重启这样的子进程。
查看start_child/2获取关于SupRef的描述。
如果对应Id的子进程标识符不存在,该函数返回{error, not_found}。如果对应的子进程标识符存在但是对应的子进程已经在运行中,该函数返回{error, running}。
如果子进程start函数返回{ok, Child} 或者{ok, Child, Info},该pid被添加到监督进程,该函数返回同样的值。
如果子进程start函数返回ignore,pid仍然设置为undefined该函数返回{ok, underfined}。
如果子进程start函数返回一个错误元组或者一个错误的值,或者执行失败,该函数返回{error, Error}, Error是一个包含错误信息的term。
which_children(SupRef) -> [{Id, Child, Type, Modules}]
Types:
SupRef = sup_ref()
Id = child_id() | undefined
Child = child() | restarting
Type = worker()
Modules = modules()
返回一个新建的包含所有属于SupRef监督进程的子进程信息的列表。
注意在内存比较小,监控树中有很多子进程的情况下调用该函数可能会造成内存溢出的异常。
查看start_child/2获取关于SupRef的信息。
返回的关于子进程的信息有:
Id - 在子进程规范中定义或者undefined在simple_one_for_one的情况
Child:对应子进程的pid,如果该进程将被重启则是原子 restarting,或者undefined 如果没有这样一个进程。
Type:和进程规范中定义的一样
Modules:和进程规范中定义的一样
count_children(SupRef) -> PropListOfCounts
Types:
SupRef = sup_ref()
PropListOfCounts = [Count]
Count = {specs, ChildSpecCount :: integer() >= 0}
| {active, ActiveProcessCount :: integer() >= 0}
| {supervisors, ChildSupervisorCount :: integer() >= 0}
| {workers, ChildWorkerCount :: integer() >= 0}
返回一个属性列表(见proplists)包含子进程和管理进程的以下每个元素的数量:
specs:所有的子进程数量,包括死亡和存活状态
active:该监督者管理的所有运行中的子进程数量
supervisors:所有在spec list中标记child_type=superviso的子进程数量,不论该子进程是否仍然存活
workers:所有在spec list中标记child_type=worker 的子进程数量,不论该子进程是否仍然存活
check_childspecs(ChildSpecs) -> Result
Types:
ChildSpecs = [child_spec()]
Result = ok | {error, Error :: term()}
该函数以一个子进程说明列表作为参数,如果所有子进程说明语法正确怎返回ok,否则返回{error, Error}
回调函数:
Module:init(Args) -> Result
Types:
Args = term()
Result = {ok,{{RestartStrategy,MaxR,MaxT},[ChildSpec]}} | ignore
RestartStrategy = strategy()
MaxR = integer() >= 0
MaxT = integer() > 0
ChildSpec = child_spec()
无论何时一个supervisor使用supervisor:start_link/2,3启动,该函数都会被新创建的进程调用获取重启策略、最大重启频率和子进程说明。
Args是提供给start函数的参数。
RestartStrategy是重启策略,MaxR和MaxT定义了最大重启频率。[ChildSpec]是一个有效的子进程规范列表,定义了哪些子进程是该监督进程应该启动和监视的。参阅关于以上Supervisor准则的讨论。
注意当重启策略是simple_one_for_one时,子进程规范列表必须是只有一个子进程规范的列表。(Id被忽略)。在初始化过程中没有子进程被启动,所有子进程都假定动态的调用supervisor:start_child/2来启动。
该函数还可能返回ignore。
其他相关:
gen_event(3), gen_fsm(3), gen_server(3), sys(3)