15 分布式 Erlang
15.1 分布式 Erlang 系统
一个分布式 Erlang 系统由多个相互通信的 Erlang 运行时系统组成。每个这样的运行时系统称为一个节点。当使用 pid 时,不同节点之间进程之间的消息传递,以及链接和监控器是透明的。但是,注册名称是每个节点本地化的。这意味着在使用注册名称发送消息时,必须指定节点,等等。
分布式机制使用 TCP/IP 套接字实现。有关如何实现替代载体的说明,请参阅ERTS 用户指南.
启动分布式节点而不指定-proto_dist inet_tls 会使节点暴露于攻击,攻击者可能会完全访问节点,并扩展到集群。在使用不安全的分布式节点时,请确保网络配置为阻止潜在攻击者。有关如何设置安全分布式节点的详细信息,请参阅 使用 SSL 进行 Erlang 分布 用户指南。
15.2 节点
一个节点是正在执行的 Erlang 运行时系统,它使用命令行标志-name(长名称)或-sname(短名称)被赋予了一个名称。
节点名称的格式是一个原子name@host。name 是用户提供的名称。host 是完整的主机名,如果使用长名称,或者主机名的第一部分,如果使用短名称。函数node() 返回节点的名称。
示例
% erl -name dilbert ([email protected])1> node(). '[email protected]' % erl -sname dilbert (dilbert@uab)1> node(). dilbert@uab
节点名称也可以通过调用net_kernel:start/1 在运行时给出。
示例
% erl 1> node(). nonode@nohost 2> net_kernel:start([dilbert,shortnames]). {ok,<0.102.0>} (dilbert@uab)3> node(). dilbert@uab
具有长节点名称的节点无法与具有短节点名称的节点通信。
15.3 节点连接
分布式 Erlang 系统中的节点是松散连接的。第一次使用另一个节点的名称时,例如,如果调用spawn(Node,M,F,A) 或net_adm:ping(Node),则会尝试连接到该节点。
连接默认情况下是可传递的。如果节点 A 连接到节点 B,而节点 B 与节点 C 建立了连接,则节点 A 也尝试连接到节点 C。可以使用命令行标志-connect_all false 关闭此功能,请参阅 ERTS 中的erl(1) 手册页。
如果节点宕机,则所有与该节点的连接都将被移除。调用erlang:disconnect_node(Node) 会强制断开节点的连接。
nodes() 返回当前连接到的(可见)节点列表。
15.4 epmd
Erlang 端口映射守护进程epmd会在启动 Erlang 节点的每个主机上自动启动。它负责将符号节点名称映射到机器地址。请参阅 ERTS 中的epmd(1) 手册页。
15.5 隐藏节点
在分布式 Erlang 系统中,有时需要连接到一个节点,但不连接到所有其他节点。一个例子是某种 O&M 功能,用于检查系统的状态,而不干扰它。为此,可以使用隐藏节点。
隐藏节点是使用命令行标志-hidden 启动的节点。隐藏节点与其他节点之间的连接不可传递,必须显式设置。此外,隐藏节点不会显示在nodes() 返回的节点列表中。相反,必须使用nodes(hidden) 或nodes(connected)。这意味着,例如,隐藏节点不会添加到global 跟踪的节点集中。
15.6 动态节点名称
如果节点名称设置为undefined,则该节点将以特殊模式启动,成为另一个节点的临时客户端。然后,该节点将向它连接的第一个节点请求一个动态节点名称。此外,这些分布式设置将被设置
由于-dist_auto_connect 设置为never,因此必须调用net_kernel:connect_node/1 才能设置连接。如果第一个建立的连接关闭(该连接为节点提供了其动态名称),则所有其他连接也将关闭,并且该节点将丢失其动态节点名称。可以再次调用net_kernel:connect_node/1 以获取一个新的动态节点名称。如果分布式被删除然后重新设置,节点名称可能会改变。
从 Erlang/OTP 23 开始支持动态节点名称功能。临时客户端节点和第一个连接的同级节点(提供动态节点名称)都必须至少为 Erlang/OTP 23 才能正常工作。
15.7 C 节点
一个C 节点是一个用 C 语言编写的程序,它充当分布式 Erlang 系统中的隐藏节点。库Erl_Interface 包含为此目的的函数。有关 C 节点的更多信息,请参阅 Erl_Interface 应用程序和 互操作性教程。.
15.8 安全性
这里的“安全性”不指加密安全性,而是指防止意外误用的安全性,例如防止节点连接到它不打算与其通信的集群。
此外,节点之间的通信默认情况下是明文形式的。如果您需要强安全性,请参阅 SSL 应用程序用户指南中的 使用 TLS 进行 Erlang 分布。
此外,以下文本中提到的默认随机 cookie 不可预测性很低。可以使用crypto 模块中的原语生成一个更好的随机 cookie,但这仍然不会使初始握手加密安全。节点间通信仍然是明文形式的。
身份验证确定哪些节点可以相互通信。在不同 Erlang 节点的网络中,它在尽可能低的级别上内置于系统中。所有节点在连接其他节点时都使用一个魔术 cookie,它是一个 Erlang 原子。
在连接设置期间,在交换节点名称后,节点相互展示的魔术 cookie 会被比较。如果它们不匹配,则连接将被拒绝。cookie 本身从未被传输,而是使用散列挑战进行比较,尽管不是以加密安全的方式。
在启动时,节点会分配一个随机原子作为其默认魔术 cookie,并假设其他节点的 cookie 为nocookie。然后,Erlang 网络身份验证服务器 (auth) 的第一个操作是搜索 用户主目录 中名为.erlang.cookie 的文件,然后搜索 filename:basedir(user_config, "erlang") 中的该文件。如果这两个文件都不存在,则会在用户主目录中创建一个.erlang.cookie 文件。文件的 UNIX 权限模式设置为八进制 400(用户只读),其内容是一个随机字符串。从文件内容创建原子Cookie,并使用erlang:set_cookie(Cookie) 将本地节点的 cookie 设置为该原子。这将设置本地节点将用于所有其他节点的默认 cookie。
因此,具有相同 cookie 文件的组用户将获得可以自由通信的 Erlang 节点,因为它们使用相同的魔术 cookie。想要运行 cookie 文件位于不同文件系统的节点的用户,必须确保它们的 cookie 文件相同。
对于使用魔术 cookie Cookie 的节点 Node1 来说,为了能够连接到另一个使用不同 cookie DiffCookie 的节点 Node2 以及接受来自该节点的连接,必须先在 Node1 上调用函数 erlang:set_cookie(Node2, DiffCookie)。拥有多个主目录(不同的 cookie 文件)的分布式系统可以通过这种方式处理。
通过这种设置,Node1 和 Node2 就达成了一致,使用哪个 cookie:Node1 对 Node2 使用显式配置的 DiffCookie,而 Node2 使用其默认 cookie DiffCookie。
您也可以使用一个既不是 Node1 也不是 Node2 的默认 cookie 的 DiffCookie,前提是在建立连接之前,您还在 Node2 中调用 erlang:set_cookie(Node1, DiffCookie)。
由于节点名称是在建立连接之前在选择 cookie 之前进行交换的,因此无论哪个节点发起连接,连接设置都能正常工作。
请注意,将 Node1 配置为在与 Node2 通信时使用 Node2 的默认 cookie,反之亦然会导致配置错误(如果 cookie 不同),因为这样两个节点都会使用对方(不同的)cookie。
当在两个节点之间建立连接时,默认行为是立即连接所有其他可见节点。这样,网络始终保持完全连接状态。如果存在具有不同 cookie 的节点,这种方法可能不合适(因为为所有可能的节点配置不同的 cookie 可能不可行),必须设置命令行标志 -connect_all false,请参阅 ERTS 中的 erl(1) 手册页。
可以通过调用 erlang:get_cookie() 来检索本地节点的魔术 cookie。
15.9 分布式 BIF
一些对分布式编程有用的 BIF(更多信息请参阅 ERTS 中的 erlang(3) 手册页)
BIF | 描述 |
erlang:disconnect_node(Node) | 强制断开与节点的连接。 |
erlang:get_cookie() | 返回当前节点的魔术 cookie。 |
erlang:get_cookie(Node) | 返回节点 Node 的魔术 cookie。 |
is_alive() | 如果运行时系统是一个节点,并且可以连接到其他节点,则返回 true,否则返回 false。 |
monitor_node(Node, true|false) | 监控 Node 的状态。如果与该节点的连接断开,则会收到消息 {nodedown, Node}。 |
node() | 返回当前节点的名称。允许在保护子句中使用。 |
node(Arg) | 返回 Arg(pid、引用或端口)所在的节点。 |
nodes() | 返回该节点连接到的所有可见节点的列表。 |
nodes(Arg) | 根据 Arg 的不同,该函数可以返回可见节点列表,也可以返回隐藏节点列表、先前已知节点列表等等。 |
erlang:set_cookie(Cookie) | 设置魔术 cookie Cookie,用于连接所有没有使用 erlang:set_cookie/2 设置显式 cookie 的节点。 |
erlang:set_cookie(Node, Cookie) | 设置连接 Node 时使用的魔术 cookie。如果 Node 是当前节点,则 Cookie 用于连接所有没有使用此函数设置显式 cookie 的节点。 |
spawn[_link|_opt](Node, Fun) | 在远程节点上创建一个进程。 |
spawn[_link|opt](Node, Module, FunctionName, Args) | 在远程节点上创建一个进程。 |
15.10 分布式命令行标志
用于分布式编程的命令行标志示例(更多信息请参阅 ERTS 中的 erl(1) 手册页)
命令行标志 | 描述 |
-connect_all false | 仅使用显式连接设置。 |
-hidden | 将节点设为隐藏节点。 |
-name Name | 将运行时系统设为节点,使用长节点名称。 |
-setcookie Cookie | 等同于调用 erlang:set_cookie(Cookie)。 |
-setcookie Node Cookie | 等同于调用 erlang:set_cookie(Node, Cookie)。 |
-sname Name | 将运行时系统设为节点,使用短节点名称。 |
15.11 分布式模块
对分布式编程有用的模块示例
在 Kernel 应用程序中
模块 | 描述 |
global | 全局名称注册工具。 |
global_group | 将节点分组到全局名称注册组中。 |
net_adm | 各种 Erlang 网络管理例程。 |
net_kernel | Erlang 网络内核。 |
在 STDLIB 应用程序中
模块 | 描述 |
slave | 启动和控制从节点。 |