# Erlang supervisor监控重启策略

我曾经说过,监督者使用起来很简单,这不是一句玩笑话。我们只需要提供一个回调函数:init/1。麻烦的地方在于这个函数的返回值非常复杂。下面是一个返回值的例子:

{ok, {{one_for_all, 5, 60},
[{fake_id,
{fake_mod, start_link, [SomeArg]},
permanent,
5000,
worker,
[fake_mod]},
{other_id,
{event_manager_mod, start_link, []},
transient,
infinity,
worker,
dynamic}]}}.
1
2
3
4
5
6
7
8
9
10
11
12
13

这些内容是什么意思?下面给出的是这个返回值的一般形式定义,更易于解释一些:

{ok, {{RestartStrategy, MaxRestart, MaxTime},[ChildSpec]}}.
1

我们来逐条讲解。

# 重启策略

上述定义中的 RestartStrategy 的值可以为 one_for_one、 one_for_all、rest_for_one 以及 simple_one_for_one。
1. one_for_one
one_for_one 是个很容易理解的重启策略。它指的是,如果监督者监督了很多工作者,当其中的一个工作者失败时,只需重启这个工作者即可。当被监督的进程都是独立的、互不相关的,或者即便这些进程重启后丢失了自己的状态,也不会对其兄弟进程产生影响时,可以使用one_for_one 策略。

2. one_for_all
one_for_all 和 musketeers①没有任何关系。当所有的工作者进程都受同一个监督者监督,且这些工作者进程必须互相依赖才能正常工作时,就使用这个策略。假如,我们想在第 15 章中实现的交易系统之上增加一个监督者。 买卖双方,任何一方崩溃了,只重启失败的这一方是没有意义的, 因为双方的交易状态会失去同步。同时重启交易的双方才是一个明智的选择, one_for_all 就是专门解决此类问题的策略。

3. rest_for_one
rest_for_one 是一个更加特殊的策略。当需要启动一组进程,而这些进程互相依赖形成一条链(A 启动 B, B 启动 C, C 启动 D,以此类推)时,可以使用 rest_for_one。 此外,如果有一些服务,它们之间存在类似的依赖关系(X 独立运行, Y 依赖于 X, Z 同时依赖于 X 和Y),也可以使用这种策略。简单来讲,使用 rest_for_one 重启策略,如果一个进程死了,那么所有在这个进程之后启动的进程(依赖于该进程)都将被重启,反之不然。

4. simple_one_for_one
虽然叫这个名字,但是 simple_one_for_one 这个重启策略一点都不简单。这种类型的监督者只监督一种子进程,当希望以动态的方式向监督者中增加子进程,而不是静态启动子进程时,可以使用这种策略。
换种说法, simple_one_for_one 监督者就待在那里,它知道自己只能创建一种类型的子进程。当需要 一个新的子 进程时,向它 发起请求, 就能得到一个 。理论上来 讲,用标准的one_for_one 监督者也可以达到这个目的,但是使用这个更简单的策略会有一些实际的好处,在本章后面讲到动态监督时,你会看到。

注 意 one_for_one 和 simple_one_for_one 两 者 乊 间 有 一 个 很 大 的 不 同 。one_for_one 监督者会把它启动的所有子进程保存在一个列表中(包括那些曾经启动的,如果你手工操作后没有清除的话), 按进程启动顺序排序, 而 simple_one_for_one 只保存一份针对所有子进程的定义,并用字典来保存子进程数据。一般来说,如果子进程的数量很多,那么当某一个子进程崩溃时, simple_one_for_one 监督者要快很多。

# 重启限制

RestartStrategy 元组中剩余的两个变量是 MaxRestart 和 MaxTime。它们的意思是,如果在 MaxTime(以秒为单位)指定的时间内,重启次数超过了 MaxRestart 指定的数字,那么监督者会放弃重启并终止所有子进程,然后自杀,永远停止运行。 这两个变量是针对监督者管辖的所有子进程的重启来说的,不是单独针对某个子进程的。好在该监督者之上的监督者可能对其子进程还抱有希望,也许会重新启动它们。

# 子进程规格说明

现在来看看返回值中的 ChildSpec 部分。 ChildSpec 表示子进程规格说明。前面的例子中有如下两个子进程的规格说明:

[{fake_id,
{fake_mod, start_link, [SomeArg]},
permanent,
5000,
worker,
[fake_mod]},
{other_id,
{event_manager_mod, start_link, []},
transient,
infinity,
worker,
dynamic}]
1
2
3
4
5
6
7
8
9
10
11
12

子进程规格说明可以描述成如下更为抽象的形式:

{ChildId, StartFunc, Restart, Shutdown, Type, Modules}.
1

接下来我们看看每个部分都是什么意思。
1. ChildId
ChildId 只是监督者内部使用的一个名称。除了在调试或者想获取监督者的所有子进程列表时,这个名字会比较有用之外,基本上没有其他用途。可以把任意的数据项作为这个标识使用,不过,我建议最好选择一些易读的名字,以备调试使用。
2. StartFunc
StartFunc 是一个元组,用来指定子进程的启动方式。它采用了标准的{M,F,A}格式,这个格式前面我们已经多次使用过。注意,这里的启动函数是 OTP 兼容的,在执行时会和调用者 进 程 链 接 在 一 起 , 这 一 点 非 常 重 要 。(提 示 : 始 终 记 住 , 在 自 己 的 模 块 中 调 用gen_*:start_link)。
3. Restart
Restart 指定了监督者在某个特定的子进程死后的处理方式,它可以取如下 3 个值:
 permanent;
 temporary;

Last Updated: 4/23/2021, 12:47:12 PM