分布式任务和配置 | Distributed tasks and configuration

分布式任务和配置

本章是Mix和OTP指南的一部分,它取决于本指南的前几章。有关更多信息,请阅读简介指南或查看边栏中的章节索引。

在最后一章中,我们将回到:kv应用程序并添加一个路由层,以便我们根据存储区名称在节点之间分配请求。

路由层将收到以下格式的路由表:

[{?a..?m, :"[email protected]"}, {?n..?z, :"[email protected]"}]

路由器将根据表检查存储桶名称的第一个字节,并根据此信息分配到适当的节点。例如,以字母“a”(?a代表字母“a” 的Unicode代码点)开头的存储桶将被分派给节点[email protected]

如果匹配条目指向评估请求的节点,那么我们已经完成了路由,并且此节点将执行请求的操作。如果匹配条目指向另一个节点,我们会将请求传递给此节点,该节点将查看其自己的路由表(可能与第一个节点中的路由表不同)并采取相应措施。如果没有条目匹配,则会引发错误。

您可能想知道为什么我们不告诉我们在路由表中找到的节点直接执行请求的操作,而是将路由请求传递到该节点进行处理。虽然像上面那样简单的路由表可以在所有节点之间合理共享,但以这种方式传递路由请求使得在应用程序增长时将路由表分解为更小的部分变得更简单。也许在某些时候,[email protected]只负责路由桶请求,并且它处理的桶将被分派到不同的节点。通过这种方式,[email protected]不需要知道有关这种变化的任何信息。

注意:在本章中,我们将在同一台机器上使用两个节点。您可以在同一网络上自由使用两台(或更多台)不同的机器,但您需要做一些准备工作。首先,您需要确保所有机器都~/.erlang.cookie具有完全相同的文件。其次,您需要保证epmd在未被阻止的端口上运行(您可以运行epmd -d调试信息)。第三,如果您想了解更多关于一般分发的信息,我们推荐您学习一些Erlang的这个伟大的Distribunomicon章节。

我们的第一个分发代码

Elixir提供设施连接节点并在它们之间交换信息。事实上,在分布式环境中工作时,我们使用相同的流程概念,消息传递和接收消息,因为Elixir流程是位置透明的。这意味着在发送消息时,无论收件人进程是在同一个节点上还是在另一个节点上,VM都将能够在两种情况下传递消息。

为了运行分布式代码,我们需要使用名称来启动虚拟机。名称可以很短(在同一网络中)或较长(需要完整的计算机地址)。让我们开始一个新的IEx会话:

$ iex --sname foo

您现在可以看到提示稍有不同,并显示节点名称后跟计算机名称:

Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help) iex([email protected])1>

我的电脑命名为jv,所以我在上面的例子中看到[email protected],但是你会得到不同的结果。 我们将在以下示例中使用[email protected],您应该在尝试代码时相应地更新它们。

我们来定义一个Hello在这个shell中命名的模块:

iex> defmodule Hello do ...> def world, do: IO.puts "hello world" ...> end

如果您在安装了Erlang和Elixir的同一网络中安装了另一台计算机,则可以启动另一台计算机。如果你不这样做,你可以在另一个终端上启动另一个IEx会话。无论哪种情况,都要给它简称bar

$ iex --sname bar

请注意,在这个新的IEx会话中,我们无法访问Hello.world/0

iex> Hello.world ** (UndefinedFunctionError) undefined function: Hello.world/0 Hello.world()

但是,我们可以在[电子邮件保护]的[电子邮件保护]上产生新的过程! 让我们试一试(其中@ computer-name是您在本地看到的那个):

iex> Node.spawn_link :"[email protected]", fn -> Hello.world end #PID<9014.59.0> hello world

Elixir在另一个节点上产生了一个进程并返回了它的PID。 然后代码在Hello.world/0函数存在的另一个节点上执行并调用该函数。 请注意,“hello world”的结果打印在当前节点栏上,而不是foo上。 换句话说,要打印的消息从foo发回给bar。 发生这种情况是因为另一个节点(foo)上产生的进程仍然具有当前节点(bar)的组长。 我们在IO章节中简要地谈到了组长。

像往常一样,我们可以通过Node.spawn_link / 2返回的pid发送和接收消息。 让我们尝试一个快速乒乓的例子:

iex> pid = Node.spawn_link :"[email protected]", fn -> ...> receive do ...> {:ping, client} -> send client, :pong ...> end ...> end #PID<9014.59.0> iex> send pid, {:ping, self()} {:ping, #PID<0.73.0>} iex> flush() :pong :ok

从我们的快速探索中,我们可以得出结论:每次我们需要执行分布式计算时,我们都应该使用Node.spawn_link / 2在远程节点上产生进程。 但是,我们通过本指南了解到,如果可能的话应该避免监督树之外的产卵过程,因此我们需要寻找其他选择。

Node.spawn_link / 2有三种更好的选择,我们可以在我们的实现中使用它们:

  • 我们可以使用Erlang的:rpc模块来执行远程节点上的功能。 在上面的[email protected] shell中,你可以调用:rpc.call(:“[email protected]”,Hello,:world,[]),它会打印出“hello world”

  • 我们可以在另一个节点上运行服务器,并通过GenServer API向该节点发送请求。 例如,可以使用GenServer.call({name,node},arg)或传递远程进程PID作为第一个参数来调用远程节点上的服务器

我们可以使用我们在前一章中了解到的任务,因为它们可以在本地和远程节点上生成。以上选项具有不同的属性。 两者:rpc和使用GenServer都会在单个服务器上序列化您的请求,而任务在远程节点上异步有效地运行,唯一的序列化点是supervisor完成的产生。对于我们的路由层,我们将使用 任务,但随时可以探索其他选择。同步/等待到目前为止,我们已经探索了独立开始和运行的任务,不考虑它们的返回值。 但是,有时候运行一个任务来计算一个值并在以后读取结果是很有用的。 为此,任务还提供了异步/等待模式:

  • 将伞形应用程序部署到可同时用作TCP服务器和键值存储的节点

  • 只要路由表只指向其他节点,就部署:kv_server应用程序只能用作TCP服务器

  • 当我们希望节点只能作为存储器工作时,只部署:kv应用程序(无TCP访问)

随着未来添加更多应用程序,我们可以继续以相同级别的粒度控制我们的部署,从而挑选出哪些应用程序正在生产。

您还可以考虑使用Distillery这样的工具来构建多个版本,该工具将打包选择的应用程序和配置,包括当前的Erlang和Elixir安装,因此即使运行时没有预先安装在目标系统上,我们也可以部署应用程序。

最后,我们在本章中学习了一些新的东西,它们也可以应用于:kv_server应用程序。 我们将作为练习离开下一步:

  • 更改:kv_server应用程序以从其应用程序环境中读取端口,而不是使用4040的硬编码值

  • 更改并配置:kv_server应用程序以使用路由功能,而不是直接调度到本地KV.Registry。 对于:kv_server测试,您可以使路由表指向当前节点本身

总结

在本章中,我们构建了一个简单的路由器,以探索Elixir和Erlang VM的分布式特性,并学习了如何配置其路由表。这是我们的Mix和OTP指南中的最后一章。

在整个指南中,我们已经构建了一个非常简单的分布式键值存储,以此作为探索诸如通用服务器,管理程序,任务,代理,应用程序等许多构造的机会。不仅如此,我们还为整个应用程序编写了测试,熟悉ExUnit,并学习了如何使用Mix构建工具来完成各种各样的任务。

如果你正在寻找一个分布式键值存储在生产环境中使用,你应该看看Riak,它也在Erlang虚拟机中运行。在Riak中,为了避免数据丢失,系统会复制存储桶,而不是路由器,它们使用一致的散列将存储区映射到节点。一致的哈希算法有助于减少将新节点存储桶添加到基础结构时需要迁移的数据量。

快乐编码!