监督 | Supervisor
主管行为
用于实施主管的行为模块。
主管是一个监督其他进程的进程,我们称之为子进程
。主管人员用于建立称为监督树
的分层过程结构。监督树
提供容错功能,并封装我们的应用程序如何启动和关闭。
使用此模块实施的主管具有标准的接口功能集,并包含跟踪和错误报告功能。
实例
为了定义一个主管,我们需要首先定义一个将被监督的子进程。作为一个例子,我们将定义一个代表堆栈的GenServer:
defmodule Stack do
use GenServer
def start_link(state) do
GenServer.start_link(__MODULE__, state, name: __MODULE__)
end
## Callbacks
def init(stack) do
{:ok, stack}
end
def handle_call(:pop, _from, [h | t]) do
{:reply, h, t}
end
def handle_cast{:push, h}, t) do
{:noreply, [h | t]}
end
end
该堆栈是列表中的一个小包装。它允许我们将一个元素放在堆栈的顶部,通过预先添加到列表中,并通过模式匹配来获取堆栈的顶部。
我们现在可以启动一个主管,负责启动并监督我们的堆栈过程,如下所示:
# Start the supervisor with the stack as a single child.
#
# The first element of the tuple is the module containing
# the child implementation, the second is the argument
# given to start_link, in this case a stack with `:hello`.
{:ok, pid} = Supervisor.start_link([
{Stack, [:hello]}
], strategy: :one_for_one)
# After started, we can query the supervisor for information
Supervisor.count_children(pid)
#=> %{active: 1, specs: 1, supervisors: 0, workers: 1}
请注意,在启动GenServer时,我们使用名称注册Stack
,这使我们可以直接调用它并获取堆栈中的内容:
GenServer.call(Stack, :pop)
#=> :hello
GenServer.cast(Stack, {:push, :world})
#=> :ok
GenServer.call(Stack, :pop)
#=> :world
但是,我们的堆栈服务器存在一个错误。如果我们调用:pop
并且堆栈是空的,它将会因为没有子句匹配而崩溃:
GenServer.call(Stack, :pop)
** (exit) exited in: GenServer.call(Stack, :pop, 5000)
幸运的是,由于服务器正在由一位主管进行监督,因此主管会自动启动一个新的服务器,最初的堆栈为[:hello]
:
GenServer.call(MyStack, :pop)
#=> :hello
主管支持不同的策略; 在上面的例子中,我们选择了:one_for_one
。此外,每个主管可以有许多员工和主管作为子女,每个主管都有其特定的配置,关机值和重启策略。
本文档的其余部分将介绍如何启动子进程,如何指定子进程,以及不同的监管策略等。
初始化过程
在上一节中,我们已经创建了一个有一个子类的监督员:
Supervisor.start_link([
{Stack, [:hello]}
], strategy: :one_for_one)
给出的第一个参数start_link
是一个孩子列表。在上面的例子中,我们已经通过一个元组,其中所述子是由实现Stack
模块和接收的初始参数[:hello]
上Stack.start_link/1
。
一般来说,启动子进程分三个步骤进行:
- 首先是主管的电话
Stack.child_spec([:hello])
。该功能必须返回描述过程监督方式的子规范
Stack
。当你use GenServer
,一个child_spec/1
是自动为您定义,但我们将看到何时以及如何进行配置。当管理员启动时(或在热代码升级的情况下),该功能被调用一次。
2. 该子规范
指出哪些功能调用,启动子进程的主管。默认情况下,它是start_link/1
函数,接收相同的参数,并在与child_spec/1
函数相同的模块中定义。每次需要新的子进程时都会调用此函数。例如,当我们Stack
在前一次会话中崩溃时,Stack.start_link([:hello])
再次调用它来启动一个新的堆栈。
3. 最后,Stack.start_link/1
启动一个运行的新进程Stack.init/1
,它负责设置一个对消息作出反应的进程。
:id
- 用于由主管内部识别儿童规格的值; 默认为给定的模块。如有冲突:id
,主管将拒绝初始化并要求明确的ID。该密钥是必需的。
:start
-一个带有模块-函数-args的元组将被调用以启动子进程。这把钥匙是必需的。
:restart
- 定义何时应该重新启动终止子进程的原子(请参见下面的“重新启动值”部分)。该键是可选的,默认为:permanent
。
:shutdown
- 定义应如何终止子进程的原子(请参见下面的“关闭值”部分)。该密钥是可选的,默认5000
如果类型是:worker
或:infinity
如果类型是:supervisor
。
:type
- 如果子进程是:worker
或:supervisor
。该键是可选的,默认为:worker
。
有一个叫做第六个键,:modules
很少改变,它是根据中的值自动设置的:start
。
大多数时候,您正在执行的行为模块将负责child_spec/1
为您设置适当的行为。例如,use Supervisor
将定义一个child_spec/1
其中:type
被设置为:supervisor
和:shutdown
是:infinity
。不过,如果您需要自定义某种行为,可以通过定义自己的child_spec/1
功能或通过传递选项来实现use
。例如,要指定GenServer
关闭限制为10秒(10_000毫秒)的人,可以这样做:
use GenServer, shutdown: 10_000
让我们了解一下:shutdown
和:restart
Shutdown values (:shutdown)
控件中支持下列关闭值:shutdown
备选方案:
:brutal_kill
- 子进程无条件终止使用Process.exit(child, :kill)
。
- 任何整数> = 0 - 管理员在发出Process.exit(child, :shutdown)信号后等待孩子终止的时间量(以毫秒为单位)。如果子进程没有陷阱退出,则初始:shutdown信号将立即终止子进程。如果子进程陷入退出,它将以毫秒为终止时间。如果它没有在指定时间内终止,则子过程由监督员通过无条件终止Process.exit(child, :kill)。
:infinity
- 作为整数工作,除非主管将无限期地等待孩子终止。如果儿童进程是一名主管,建议的价值是:infinity
让主管及其子女有足够的时间关闭。这个选项可以与普通员工一起使用,但是这样做是不鼓励的,需要非常小心。如果不谨慎使用并且子进程没有终止,则意味着您的应用程序永远不会终止。
:permanent
-子类进程总是重新启动。
:temporary
-无论监督战略如何,子类进程从未重新启动。
:transient
- 子进程只有在异常终止时才会重新启动,也就是说,退出的原因不是:normal
,:shutdown
或{:shutdown, term}
。
有关退出原因及其影响的更完整理解,请参见下一节“退出原因”。
退出原因
主管根据其:restart
配置重启子进程。例如,何时:restart
设置:transient
,监督员不会重新启动子进程,以防因为理由退出:normal
,:shutdown
或{:shutdown, term}
。
因此,人们可能会问:当我退出时,我应该选择哪个退出理由?有三种选择:
:normal
-在这种情况下,退出将不会被记录,在瞬态模式下不会重新启动,并且链接的进程不会退出
:shutdown
或{:shutdown, term}
-在这种情况下,退出不会被记录,在瞬态模式下不会重新启动,除非进程捕获出口,否则链接进程退出的原因相同
- 任何其他术语 - 在这种情况下,退出将被记录,在瞬态模式下重新启动,并且链接进程以相同的原因退出,除非它们陷入退出
- 您需要对主管初始化执行一些特定的操作,比如设置ETS表。
- 您希望执行部分热代码交换树。例如,如果添加或删除子级,则基于模块的监督将直接添加和删除新的子级,而动态监视则要求重新启动整棵树以执行此类交换。
:id
-子规范id,不符合当前模块
:start
- 如何启动子进程(默认为调用__MODULE__.start_link/1
)
:restart
-当主管重新启动时,默认为:permanent
start_link/2, init/2 and strategies
到目前为止,我们已经启动了主管将一个孩子作为元组传递给一个调用:one_for_one
:
Supervisor.start_link([
{Stack, [:hello]}
], strategy: :one_for_one)
或:
Supervisor.init([
{Stack, [:hello]}
], strategy: :one_for_one)
但是,可以用三种不同的格式指定子级,并且主管支持不同的选项。让我们正式定义一下。
给出的第一个参数start_link/2
是可能是以下任一种的子列表:
- 一个模块 - 比如
Stack
。在这种情况下,它相当于传递{Stack, []}
(这意味着Stack.child_spec/1
调用一个空的关键字列表)
- 一个元组,其中一个模块作为第一个元素,开始参数作为第二个
{Stack, [:hello]}
当使用这种格式时,主管将从给定模块中检索子规范。
- 表示子规范本身的映射--例如上一节中概述的子规范映射。
第二个参数是选项的关键字列表:
:strategy
- 重新启动策略选项。它可以是:one_for_one
,:rest_for_one
,:one_for_all
,或:simple_one_for_one
。请参阅“策略”部分。
:max_restarts
- 一段时间内允许的最大重启次数。默认为3
。
:max_seconds
-:max_restarts
适用的时间范围。默认为5
。
:one_for_one
-如果子进程终止,则只重新启动该进程。
:one_for_all
- 如果子进程终止,则所有其他子进程终止,然后重新启动所有子进程(包括终止的进程)。
:rest_for_one
-如果一个子进程终止,子进程的“REST”,即在终止后的子进程按开始顺序结束后的“REST”进程被终止。然后重新启动终止的子进程和其余的子进程。
:simple_one_for_one
-类似于:one_for_one
但更适合于动态附加孩子。此策略要求主管规范只包含一个子级。当使用此策略时,本模块中的许多函数的行为略有不同。
:specs
- 注销或存活的子进程总数
:active
-此主管管理的所有正在运行的子进程的计数
:supervisors
- 所有主管人员的人数,无论这些子监督员是否还活着
:workers
- 所有工作进程的数量,不管这些子进程是否仍然存在
:strategy
- 重新启动策略选项。它可以是:one_for_one
,:rest_for_one
,:one_for_all
,或:simple_one_for_one
。您可以在Supervisor
模块文档中了解有关策略的更多信息。
:max_restarts
-在一个时间框架内允许的最大重新启动量。默认为3
...
:max_seconds
-时限:max_restarts
适用。默认为5
...
该:strategy
选项是必需的,默认情况下最多可以在5秒内重新启动3次。检查Supervisor
模块以获取可用策略的详细描述。
restart_child(supervisor, child_id)
restart_child(supervisor, term) ::
{:ok, child} |
{:ok, child, term} |
{:error, error} when error: :not_found | :simple_one_for_one | :running | :restarting | term
重新启动由child_id标识的子进程。
子规范必须存在,并且相应的子进程不能运行。
注意,对于临时子程序,子规范在子程序终止时会自动删除,因此无法重新启动这些子规范。
如果子进程启动函数返回{:ok, child}
或{:ok, child, info}
,将PID添加到监控器,此函数返回相同的值。
如果子进程启动函数返回:ignore
,则PID将保持设置为:undefined
并且此函数返回{:ok, :undefined}
。
如果child_id
未找到,或者当前进程正在运行或正在重新启动。
如果子进程启动函数返回错误元组或错误值,或者如果失败,则返回{:error, error}
。
:simple_one_for_one
主管不支持此操作。
start_child(supervisor,child_spec_or_args)
start_child(supervisor, :supervisor.child_spec | [term]) :: on_start_child
动态地将子规范添加到supervisor
然后开始那个子进程。
child_spec
应该是有效的子女规范(除非主管是:simple_one_for_one
主管,见下文)。子进程将按照子规范中的定义启动。
在:simple_one_for_one
使用监督员中定义的子规范而不是a的情况下child_spec
,预期可以使用任意的术语列表。子进程将通过将给定列表附加到子规范中的现有函数参数来启动。
如果具有指定标识的子规范已经存在,将child_spec
被丢弃,并且此函数分别返回一个错误,:already_started
或者:already_present
如果相应的子进程正在运行或者没有运行。
如果子进程启动函数返回,{:ok, child}
或者{:ok, child, info}
,然后将子进程指定和PID添加到主管,并且此函数返回相同的值。
如果子进程启动函数返回:ignore
,则将子进程规范添加到主管,PID设置为:undefined
并且此函数返回{:ok, :undefined}
。
如果子进程启动函数返回错误元组或错误值,或者失败,则会丢弃子规范,并且此函数返回{:error, error}
其中error
包含有关错误和子规范信息的术语。
start_link(children, options)
start_link(module, term) :: on_start
start_link([:supervisor.child_spec | {module, term} | module], options) :: on_start
开始一个主管与给定的子进程。
子是一个模块列表,包含模块和参数的2元组元素或带有子规范的映射。需要通过:strategy
选项提供策略。有关示例和其他选项,请参阅“start_link/2,init/2和策略”。
这些选项也可用于注册管理员名称。支持的值在GenServer
模块文档的“名称注册”部分中有描述。
如果主管及其子进程成功催生了(如果每个子进程的启动函数返回{:ok, child}
,{:ok, child, info}
或:ignore
)这个函数返回{:ok, pid}
,这里pid
是主管的PID。如果管理员被赋予一个名称并且具有指定名称的进程已经存在,则该函数返回{:error, {:already_started, pid}}
,pid
该进程的PID 在哪里。
如果任何子进程的启动函数失败或返回错误元组或错误值,监督员将首先终止:shutdown
所有已启动的子进程,然后自行终止并返回{:error, {:shutdown, reason}}
。
请注意,启动此功能的主管与父进程关联,不仅会在崩溃时退出,而且如果父进程:normal
有理由退出。
start_link(module, arg, options \ [])
start_link(module, term, GenServer.options) :: on_start
用给定的module
和arg
开始一个主管进程。
要启动主管,init/1
回调将在给定的调用module
,以arg
作为其参数。init/1
回调必须返回可以在init/2
功能的帮助下创建的管理员规范。
如果init/1
回调返回:ignore
,则此函数:ignore
也会返回,并且管理程序将以原因终止:normal
。如果失败或返回的值不正确,则此函数返回{:error, term}
其中term
包含有关错误的信息的术语,并且管理程序将以原因终止term
。
:name
也可以给出该选项以注册主管人员姓名,支持的值在GenServer
模块文档的“姓名注册”部分进行描述。
stop(supervisor, reason \ :normal, timeout \ :infinity)
stop(supervisor, reason :: term, timeout) :: :ok
同时停止给定的主管reason
。
如果主管以给定的原因终止,它将返回:ok
。如果以其他原因终止,则呼叫退出。
该函数保持关于错误报告的OTP语义。如果原因不是:normal
,:shutdown
或者{:shutdown, _}
记录了错误报告。
terminate_child(supervisor, pid_or_child_id)
terminate_child(supervisor, pid | term) ::
:ok |
{:error, error} when error: :not_found | :simple_one_for_one
终止给定的子进程,由PID或子进程ID标识。
如果主管不是一个:simple_one_for_one
,则预计该子进程ID,并且该过程(如果有的话)终止; 子进程的说明一直保留,除非孩子是临时的。
在:simple_one_for_one
主管的情况下,预计PID。如果给出子规范标识符而不是a pid
,则该函数返回{:error, :simple_one_for_one}
。
主管可以稍后重新启动非临时子流程。子进程也可以通过调用显式重启restart_child/2
。使用delete_child/2
删除的子规范。
如果成功,此函数返回:ok
。如果给定子ID没有子规范,或者没有给定PID的进程,则此函数返回{:error, :not_found}
。
which_children(supervisor)
which_children(supervisor) :: [{term | :undefined, child | :restarting, :worker | :supervisor, :supervisor.modules}]
返回包含给定主管的所有子级信息的列表。
注意,在监视大量在低内存条件下的子函数时调用此函数可能导致内存不足异常。
此函数返回{id, child, type, modules}
元组,其中:
- id - 在子规范中定义或:在simple_one_for_one主管的情况下未定义
- child - 相应子进程的PID,:如果进程将要重新启动,则重新启动,或者:如果没有此进程,则为undefined
type
---:worker
或:supervisor
,由子规范指定。
modules
-子进程规格所规定的
回调
init(args)
init(args :: term) ::
{:ok, {:supervisor.sup_flags, [:supervisor.child_spec]}} |
:ignore
调用回调以启动管理程序并在热代码升级期间进行回调。