9 分布式应用程序
9.1 介绍
在具有多个 Erlang 节点的分布式系统中,可能需要以分布式方式控制应用程序。如果运行特定应用程序的节点发生故障,则需要在另一个节点上重新启动该应用程序。
这种应用程序称为**分布式应用程序**。请注意,它是应用程序的控制是分布式的。所有应用程序都可以被视为分布式应用程序,例如,它们可以使用其他节点上的服务。
由于分布式应用程序可以在节点之间迁移,因此需要一些寻址机制来确保其他应用程序可以对其进行寻址,而无论它当前在哪个节点上执行。本文档不会探讨此问题,但内核中的 global 或 pg 模块可以用于此目的。
9.2 指定分布式应用程序
分布式应用程序由应用程序控制器和分布式应用程序控制器进程 dist_ac 控制。这两个进程都是内核应用程序的一部分。因此,分布式应用程序通过使用以下配置参数配置内核应用程序来指定(另请参见 kernel(6))
distributed = [{Application, [Timeout,] NodeDesc}]
- 指定应用程序 Application = atom() 可以执行的位置。
- NodeDesc = [Node | {Node,...,Node}] 是按优先级排序的节点名称列表。元组中节点之间的顺序未定义。
- Timeout = integer() 指定在另一个节点上重新启动应用程序之前等待的毫秒数。默认值为 0。
为了使应用程序控制的分布正常工作,可以运行分布式应用程序的节点必须相互联系并协商在哪里启动应用程序。这使用内核中的以下配置参数完成
- sync_nodes_mandatory = [Node] - 指定哪些其他节点必须启动(在由 sync_nodes_timeout 指定的超时时间内)。
- sync_nodes_optional = [Node] - 指定哪些其他节点可以启动(在由 sync_nodes_timeout 指定的超时时间内)。
- sync_nodes_timeout = integer() | infinity - 指定等待其他节点启动的毫秒数。
启动后,节点将等待 sync_nodes_mandatory 和 sync_nodes_optional 指定的所有节点启动。当所有节点启动,或当所有必需节点启动并且由 sync_nodes_timeout 指定的时间过去后,所有应用程序将启动。如果并非所有必需节点都启动,则节点将终止。
示例
应用程序 myapp 应在节点 cp1@cave 上运行。如果此节点发生故障,则应在 cp2@cave 或 cp3@cave 上重新启动 myapp。节点 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@cave 和 cp3@cave 的系统配置文件相同,除了必需节点列表,对于 cp2@cave 为 [cp1@cave, cp3@cave],对于 cp3@cave 为 [cp1@cave, cp2@cave]。
所有参与的节点必须具有相同的值 distributed 和 sync_nodes_timeout。否则系统行为将未定义。
9.3 启动和停止分布式应用程序
当所有参与的(必需的)节点启动后,可以通过在**所有这些节点**上调用 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:stop(Application) 来停止应用程序。
9.4 故障转移
如果运行应用程序的节点发生故障,应用程序将在(指定超时时间后)在 distributed 配置参数的节点列表中列出的第一个运行节点上重新启动。这称为**故障转移**。
应用程序将以正常方式在新的节点上启动,即通过应用程序主进程调用
Module:start(normal, StartArgs)
例外情况是如果应用程序定义了 start_phases 键(参见 包含的应用程序)。在这种情况下,应用程序将通过调用以下内容启动
Module:start({failover, Node}, StartArgs)
这里 Node 是终止的节点。
示例
如果 cp1 发生故障,系统将检查其他节点 cp2 或 cp3 中哪一个正在运行的应用程序数量最少,但会等待 5 秒钟以使 cp1 重新启动。如果 cp1 未重新启动,并且 cp2 运行的应用程序少于 cp3,则 myapp 将在 cp2 上重新启动。
现在假设 cp2 也发生故障,并且在 5 秒钟内未重新启动。现在 myapp 将在 cp3 上重新启动。
9.5 接管
如果启动的节点根据 distributed 具有比运行分布式应用程序的节点更高的优先级,则应用程序将在新节点上重新启动,并在旧节点上停止。这称为**接管**。
应用程序将通过应用程序主进程调用以下内容启动
Module:start({takeover, Node}, StartArgs)
这里 Node 是旧的节点。
示例
如果 myapp 正在 cp3 上运行,并且 cp2 现在重新启动,它不会重新启动 myapp,因为 cp2 和 cp3 节点之间的顺序未定义。
但是,如果 cp1 也重新启动,则函数 application:takeover/2 会将 myapp 迁移到 cp1,因为 cp1 对此应用程序的优先级高于 cp3。在这种情况下,Module:start({takeover, cp3@cave}, StartArgs) 将在 cp1 上执行以启动应用程序。