查看源码 分布式应用

简介

在一个包含多个 Erlang 节点的分布式系统中,可能需要以分布式方式控制应用程序。如果某个应用程序运行的节点宕机,该应用程序需要在另一个节点上重启。

这样的应用程序称为分布式应用程序。 请注意,分布式的是应用程序的控制。所有应用程序都可以是分布式的,因为它们可以使用其他节点上的服务。

由于分布式应用程序可以在节点之间移动,因此需要某种寻址机制来确保其他应用程序可以访问它,而不管它当前在哪一个节点上执行。这里不讨论这个问题,但是可以使用 Kernel 中的 globalpg 模块来实现此目的。

指定分布式应用程序

分布式应用程序由应用程序控制器和一个名为 dist_ac 的分布式应用程序控制器进程控制。这两个进程都是 Kernel 应用程序的一部分。因此,分布式应用程序通过配置 Kernel 应用程序来指定,使用以下配置参数(另请参见Kernel):

distributed = [{Application, [Timeout,] NodeDesc}]

  • 指定应用程序 Application = atom() 可以执行的位置。
  • NodeDesc = [Node | {Node,...,Node}] 是按优先级顺序排列的节点名称列表。元组中节点之间的顺序是未定义的。

  • Timeout = integer() 指定在另一个节点上重启应用程序之前等待的毫秒数。默认值为 0。

为了使应用程序控制的分布式正常工作,分布式应用程序可以运行的节点必须相互联系并协商在哪里启动应用程序。这是使用 Kernel 中的以下配置参数完成的:

  • sync_nodes_mandatory = [Node] - 指定必须启动的其他节点(在 sync_nodes_timeout 指定的超时时间内)。
  • sync_nodes_optional = [Node] - 指定可以启动的其他节点(在 sync_nodes_timeout 指定的超时时间内)。
  • sync_nodes_timeout = integer() | infinity - 指定等待其他节点启动的毫秒数。

启动时,节点会等待 sync_nodes_mandatorysync_nodes_optional 指定的所有节点启动。当所有节点都启动时,或者当所有强制节点都启动且 sync_nodes_timeout 指定的时间已过时,所有应用程序都会启动。如果并非所有强制节点都启动,则节点将终止。

示例

应用程序 myapp 将在节点 cp1@cave 上运行。如果此节点宕机,则 myapp 将在 cp2@cavecp3@cave 上重启。 cp1@cave 的系统配置文件 cp1.config 可以如下所示:

[{kernel,
  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
   {sync_nodes_timeout, 5000}
  ]
 }
].

cp2@cavecp3@cave 的系统配置文件是相同的,除了强制节点列表,cp2@cave 的强制节点列表为 [cp1@cave, cp3@cave]cp3@cave 的强制节点列表为 [cp1@cave, cp2@cave]

注意

所有涉及的节点必须具有相同的 distributedsync_nodes_timeout 值。否则,系统行为是未定义的。

启动和停止分布式应用程序

当所有涉及的(强制)节点都已启动后,可以通过在所有这些节点上调用 application:start(Application) 来启动分布式应用程序。

可以使用引导脚本(请参阅发布)自动启动应用程序。

应用程序在 distributed 配置参数中的节点列表中列出的第一个可操作的节点上启动。应用程序像往常一样启动。也就是说,会创建一个应用程序主进程,并调用应用程序回调函数

Module:start(normal, StartArgs)

示例

继续前一节的示例,启动三个节点,并指定系统配置文件

> erl -sname cp1 -config cp1
> erl -sname cp2 -config cp2
> erl -sname cp3 -config cp3

当所有节点都可操作时,可以启动 myapp。这可以通过在所有三个节点上调用 application:start(myapp) 来实现。然后,它将在 cp1 上启动,如下图所示:

Application myapp - Situation 1

类似地,必须通过在所有涉及的节点上调用 application:stop(Application) 来停止应用程序。

故障转移

如果应用程序运行所在的节点宕机,则会在(指定的超时时间之后)distributed 配置参数中节点列表中列出的第一个可操作的节点上重启该应用程序。这称为故障转移

应用程序在新节点上以正常方式启动,即通过应用程序主进程调用

Module:start(normal, StartArgs)

例外情况是,如果应用程序定义了 start_phases 键(请参阅包含的应用程序)。然后,该应用程序改为通过调用以下函数启动:

Module:start({failover, Node}, StartArgs)

此处 Node 是终止的节点。

示例

如果 cp1 宕机,系统会检查其他节点 cp2cp3 中哪个节点运行的应用程序最少,但会等待 5 秒钟让 cp1 重启。如果 cp1 没有重启,并且 cp2 运行的应用程序少于 cp3,则 myapp 将在 cp2 上重启。

Application myapp - Situation 2

假设现在 cp2 也宕机并且在 5 秒内没有重启。 myapp 现在将在 cp3 上重启。

Application myapp - Situation 3

接管

如果启动了一个节点,该节点根据 distributed 配置的优先级高于运行分布式应用程序的节点,则该应用程序将在新节点上重启,并在旧节点上停止。这称为接管

应用程序由应用程序主进程调用以下函数启动:

Module:start({takeover, Node}, StartArgs)

此处 Node 是旧节点。

示例

如果 myappcp3 上运行,并且如果 cp2 现在重启,则它不会重启 myapp,因为 cp2cp3 节点之间的顺序是未定义的。

Application myapp - Situation 4

但是,如果 cp1 也重启,则函数 application:takeover/2 会将 myapp 移动到 cp1,因为对于此应用程序,cp1 的优先级高于 cp3。在这种情况下,会在 cp1 上执行 Module:start({takeover, cp3@cave}, StartArgs) 以启动应用程序。

Application myapp - Situation 5