监督 | 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
,它负责设置一个对消息作出反应的进程。
总而言之,当它Supervisor.start_link(children, opts)
被调用时,它遍历儿童列表并检索它们child_spec/1
。然后每个孩子的规范描述了每个孩子是如何开始的,通常是通过start_link/1
函数。主管调用start_link/1
它初始化的时间以及子进程何时需要重启。作为第一步,开始的新进程start_link/1
经常执行init
回调。该init
回调是我们初始化和配置子进程。
子规范
儿童规范描述了主管应该如何启动和监督儿童流程。我们了解到,当我们调用时 use GenServer
,a Stack.child_spec/1
会自动为我们定义。我们来调用它并查看它返回的内容:
Stack.child_spec([:hello]) #=> %{id:Stack,start: {Stack,:start_link, [[:hello]]}, restart::permanent, shutdown:5000, type::worker }
子规格包含5个键。前两个是必需的,其余的是可选的:
: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
让主管及其子女有足够的时间关闭。这个选项可以与普通员工一起使用,但是这样做是不鼓励的,需要非常小心。如果不谨慎使用并且子进程没有终止,则意味着您的应用程序永远不会终止。
重新启动值(:重新启动)
该:restart
选项控制主管应认为是否成功终止。如果终止成功,主管将不会重新启动孩子。如果子进程崩溃,主管将启动一个新进程。
该:restart
选项支持以下重新启动值:
:permanent
-子类进程总是重新启动。
:temporary
-无论监督战略如何,子类进程从未重新启动。
:transient
- 子进程只有在异常终止时才会重新启动,也就是说,退出的原因不是:normal
,:shutdown
或{:shutdown, term}
。
有关退出原因及其影响的更完整理解,请参见下一节“退出原因”。
退出原因
主管根据其:restart
配置重启子进程。例如,何时:restart
设置:transient
,监督员不会重新启动子进程,以防因为理由退出:normal
,:shutdown
或{:shutdown, term}
。
因此,人们可能会问:当我退出时,我应该选择哪个退出理由?有三种选择:
:normal
-在这种情况下,退出将不会被记录,在瞬态模式下不会重新启动,并且链接的进程不会退出
:shutdown
或{:shutdown, term}
-在这种情况下,退出不会被记录,在瞬态模式下不会重新启动,除非进程捕获出口,否则链接进程退出的原因相同
- 任何其他术语 - 在这种情况下,退出将被记录,在瞬态模式下重新启动,并且链接进程以相同的原因退出,除非它们陷入退出
注意到达到最大重启强度的主管将:shutdown
有理由退出 。在这种情况下,只有在其:restart
选项被设置为:permanent
(默认)的情况下定义其子规格时,管理程序才会重新启动。
基于模块的主管
在上面的例子中,通过将监督结构传递给监督员start_link/2
。但是,监督人员也可以通过明确定义监督模块来创建:
defmoduleMyApp.Supervisor do# Automatically defines child_spec/1useSupervisordefstart_link(arg) doSupervisor.start_link(__MODULE__, arg, name:__MODULE__)enddefinit(_arg)doSupervisor.init([ {Stack, [:hello]} ], strategy::one_for_one) endend
这两种方法之间的区别在于,基于模块的主管可以更直接地控制主管如何初始化。Supervisor.start_link/2
我们没有使用自动初始化的孩子列表进行调用,而是在init/1
回调的旁边定义了一位主管,并通过调用手动初始化了孩子,并Supervisor.init/2
传递了我们将要提供的相同参数start_link/2
。
在下列情况下您可能需要使用基于模块的主管:
- 您需要对主管初始化执行一些特定的操作,比如设置ETS表。
- 您希望执行部分热代码交换树。例如,如果添加或删除子级,则基于模块的监督将直接添加和删除新的子级,而动态监视则要求重新启动整棵树以执行此类交换。
注意
use Supervisor
定义了一个child_spec/1
函数,允许将定义的模块置于监督树下。生成的child_spec/1
可以使用以下选项进行自定义:
: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
。
该:strategy
选项是必需的,默认情况下最多可以在5秒内重新启动3次。
策略
管理器支持不同的监督策略(通过:strategy
选项,如上所示):
:one_for_one
-如果子进程终止,则只重新启动该进程。
:one_for_all
- 如果子进程终止,则所有其他子进程终止,然后重新启动所有子进程(包括终止的进程)。
:rest_for_one
-如果一个子进程终止,子进程的“REST”,即在终止后的子进程按开始顺序结束后的“REST”进程被终止。然后重新启动终止的子进程和其余的子进程。
:simple_one_for_one
-类似于:one_for_one
但更适合于动态附加孩子。此策略要求主管规范只包含一个子级。当使用此策略时,本模块中的许多函数的行为略有不同。
简单的one for one
:simple_one_for_one
当您想要动态启动和停止受监督的孩子时,主管是很有用的。作为一个例子,让我们动态地启动多个代理来保持状态。
:simple_one_for_one
主管人员的一个重要方面是,我们经常想在:start
以后动态地启动孩子时传递参数,而不是在定义孩子规范时。在这种情况下,我们不应该这样做
Supervisor.start_link [ {Agent,fn -> 0end} ]
因为上面的例子会强制所有代理具有相同的状态。在这种情况下,我们可以使用该child_spec/2
函数来构建和覆盖子规范中的字段:
# Override the :start field to have no args.
# The second argument has no effect thanks to it. agent_spec = Supervisor.child_spec(Agent,start: {Agent,:start_link, []})
# We start a supervisor with a simple one for one strategy.
# The agent won't be started now but later on. {:ok, sup_pid} = Supervisor.start_link([agent_spec], strategy::simple_one_for_one)
# No child worker is active until start_child is calledSupervisor.count_children(sup_pid)
#=> %{active: 0, specs: 1, supervisors: 0, workers: 0}
一个策略中的简单策略只能定义一个孩子,当我们调用时,它可以作为模板start_child/2
。
主管启动后,让我们动态地启动代理:
{:ok, agent1} = Supervisor.start_child(sup_pid, [fn -> 0end]) Agent.update(agent1, & &1 + 1) Agent.get(agent1, & &1) #=> 1 {:ok, agent2} = Supervisor.start_child(sup_pid, [fn -> %{} end]) Agent.get(agent2, & &1) #=> %{}Supervisor.count_children(sup_pid) #=> %{active: 2, specs: 1, supervisors: 0, workers: 2}
名称注册
监督人员必须与GenServer
。相同的名称注册规则。在文档中阅读有关这些规则的更多信息GenServer
。
总结
类型
主管规范
给予start_link/2
和的选项init/2
主管的名称
start_link
函数的返回值
start_child
函数的返回值
start*
功能使用的选项值
start*
功能使用的选项
支持的策略
supervisor参考
函数
child_spec(module_or_map, overrides)
构建并覆盖子规范
返回包含给定超级用户计数值的地图
delete_child(supervisor, child_id)
删除由标识的子规范child_id
接收要初始化的儿童列表和一组选项
restart_child(supervisor, child_id)
重新启动由标识的子进程child_id
start_child(supervisor, child_spec_or_args)
动态地添加一个子规范supervisor
并启动该子项
与给定的子类开始一位主管
start_link(module, arg, options \\ [])
用给定的module
和开始一个主管进程arg
stop(supervisor, reason \\ :normal, timeout \\ :infinity)
同时停止给定的主管reason
terminate_child(supervisor, pid_or_child_id)
终止给定的子类,由PID或孩子ID标识
返回包含给定主管的所有子项的信息的列表
回调
调用回调以启动管理程序并在热代码升级期间进行回调
类型
child()View Source
child() :: pid | :undefined
child_spec()View Source
child_spec() :: :supervisor.child_spec
Supervisor规范
flag()View Source
flag ::
{:strategy, strategy} |
{:max_restarts, non_neg_integer} |
{:max_seconds, pos_integer}
给予start_link/2
和的选项init/2
name()View Source
name() :: atom | {:global, term} | {:via, module, term}
Supervisor名称
on_start()View Source
on_start ::
{:ok, pid} |
:ignore |
{:error, {:already_started, pid} | {:shutdown, term} | term}
start_link
函数的返回值
on_start_child()View Source
on_start_child ::
{:ok, child} |
{:ok, child, info :: term} |
{:error, {:already_started, child} | :already_present | term}
start_child
函数的返回值
option()View Source
option() :: {:name, name} | flag
start*
函数使用的选项值
options()View Source
options() :: [option, ...]
start*
函数使用的选项
strategy()View Source
strategy ::
:simple_one_for_one |
:one_for_one |
:one_for_all |
:rest_for_one
支持的策略
supervisor()View Source
supervisor() :: pid | name | {atom, node}
Supervisor安靠
函数
child_spec(module_or_map, overrides)View Source
child_spec(child_spec | {module, arg :: term} | module, keyword) :: child_spec
构建并覆盖子规范。
类似start_link/2
并且init/2
,它需要一个 module
,{module, arg}
或地图作为子规范。如果给出模块,则通过调用来检索规范 module.child_spec(arg)
。
在检索子规范之后,这些字段config
将直接应用于子规范。如果config
有不映射到任何子规范字段的键,则会引发错误。
请参阅模块文档中的“子规范”部分,了解覆盖的所有可用键。
例子
:id
当监视树中需要多次启动相同的模块时,此功能通常用于设置选项:
Supervisor.child_spec({Agent, fn -> :ok end}, id: {Agent, 1})
#=> %{id: {Agent, 1},
start: {Agent, :start_link, [fn -> :ok end]}}
当在:simple_one_for_one
策略下启动模块时需要更改参数的数量时也可以使用它,因为大多数参数可以动态给定:
Supervisor.child_spec(Agent, start: {Agent, :start_link, []})
#=> %{id: Agent,
start: {Agent, :start_link, []}}
count_children(supervisor)View Source
count_children(supervisor) :: %{specs: non_neg_integer, active: non_neg_integer, supervisors: non_neg_integer, workers: non_neg_integer}
返回包含给定超级用户计数值的地图。
该地图包含以下键:
:specs
- 注销或存活的子进程总数
:active
-此主管管理的所有正在运行的子进程的计数
:supervisors
- 所有主管人员的人数,无论这些子监督员是否还活着
:workers
- 所有工作进程的数量,不管这些子进程是否仍然存在
delete_child(supervisor, child_id)View Source
delete_child(supervisor, term) ::
:ok |
{:error, error} when error: :not_found | :simple_one_for_one | :running | :restarting
删除由标识的子规范child_id
。
相应的子进程不能运行;terminate_child/2
如果它正在运行,用它来终止它。
如果成功,此函数返回:ok
。如果child_id
未找到该函数,或者当前进程正在运行或正在重新启动,则此函数可能会返回错误并显示相应的错误元组。
:simple_one_for_one
主管不支持此操作。
init(children, options)View Source
init([child_spec | {module, term} | module], flag) :: {:ok, tuple}
接收要初始化的子列表和一组选项。
这通常在init/1
基于模块的管理者回调结束时调用。有关详细信息,请参阅模块文档中的“基于模块的管理程序”和“start_link / 2,init / 2和策略”部分。
这个函数返回一个包含主管标志和子规范的元组。
示例
def init(_arg) do
Supervisor.init([
{Stack, [:hello]}
], strategy: :one_for_one)
end
选项
: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
调用回调以启动管理程序并在热代码升级期间进行回调。
本文档系腾讯云开发者社区成员共同维护,如有问题请联系 cloudcommunity@tencent.com