Elixir OTP主管可以“使用Supervisor”还是必须“使用Genserver”?

时间:2017-04-22 07:13:19

标签: elixir otp supervisor gen-server

所以看一些例子,我有点困惑:

elixir docs(https://github.com/Azure/azure-mobile-apps-net-server)似乎建议您只使用“使用主管”宏来构建监督树。

但我在博客文章/随机互联网搜索中看到的所有示例都“使用Genserver”。那么监督树必须使用Genserver接口吗?

我想我需要一些指向正确方向的指针,可能需要查看代码或更清晰的示例。

2 个答案:

答案 0 :(得分:2)

在复杂的应用程序中设计监督树可能是构建opt应用程序最具挑战性的方面之一。但是,在大多数情况下,我发现大多数应用程序都需要这种复杂性。

首先,将主管的概念与他们监督的流程相结合。即GenServers,代理,任务,GenFSM等。

对于许多简单的应用程序/网络应用程序,平面管理器树将起作用。工作流程是这样的;

  • 添加新的GenServer模块
  • 将该模块的启动添加到主管worker(NewServer, [])

但是,对于更复杂的解决方案,您可能希望在发生故障时更好地控制链接哪些服务器。您可以在此处向管理程序树引入另一个层。在这种情况下,您将添加一个新的管理程序模块并启动它,如

# application
children = [
  worker(WorkerMod1, args),
  worker(WorkerMod2, args2),
  supervisor(NextLevelSup, sargs)
]
# ....

# next_level_sup
children = [
  worker(GenServer1Grp1, args, id: "some unique id", restart: :temporary),
  worker(GenServer2Grp2, args2, id: "id2", restart: :transient),
]
# ...

然后选择要用于新主管的重启策略。

tl; dr每个服务器进入一个模块。每个服务器都是从不同模块中的主管启动的。

我的一个Elixir应用程序(我设计的第一个)具有多个级别的监督。您可以在this video TS 12:10

上找到更多信息

答案 1 :(得分:0)

要回答标题中的问题,use Supervisor本身就足够了。

我认为,您在《 Elixir混合和OTP指南》中提到的一章令人困惑的是,大多数示例是针对示例项目的,该示例项目实际上有效地实现了某些Supervisor(更容易)的实现。用于。

一个上司可能最清楚地被理解为类似于特殊的GenServerSupervisor本身调用了几个GenServer函数:

GenServer是一个“通用服务器”。 Supervisor就像是一台特殊的服务器,(几乎总是) 监督其他服务器(包括其他主管)。

但是,由于主管是特殊的,所以他们通常只关心对 other 个“通用”服务器的监督,而本身并不封装任何其他逻辑。

在许多博客文章和搜索结果中的示例中经常看到“ use GenServer”的原因是,GenServer更为有用,因为它实际上有效地限制了所有可能的“通用服务器可能想要的。主管甚至整个监督树都无聊得多,因为它通常只处理崩溃时重新启动受监管的流程。

您可能有大量的工作人员可以与之互动,例如外部服务。可以使用use GenServer(或use Agent)很好地实现这些工作程序。您应用的其他一些组件需要与这些工作人员进行交互,例如给他们工作。那应该封装为它自己的过程,例如作为GenServer(或Registry)。

要确保工作人员崩溃时重新启动,并且同样要确保注册表崩溃时重新启动注册表,您可以使用管理员来这样做。

在Elixir混合和OTP指南中,正在运行的示例是分布式键值存储。第一章主要只是Mix的介绍。

The second chapter涵盖了分布式系统(例如同时。它使用Agent,这基本上是一个具有某些状态的进程,可以同时对其进行检索和更新。

The third chapter涵盖了GenServer,但请注意,上一章中使用Agent实现的基本键值存储未修改为使用GenServer。相反,本章介绍构建“流程注册表”,即管理多个键值存储并按名称访问它们的方法。请注意,它的大多数功能也可以使用Agent来实现(它本身是建立在GenServer之上的。)

注册表实际上也是 一个单独的键值存储-其中键是 other 键值存储的名称,而值是运行时的引用这些其他商店的流程。

本章末尾介绍了过程监视,并使用它来处理(Agent)键值存储过程崩溃的情况。如果不监视这些进程,就无法知道它们何时崩溃以及因此何时从注册表中删除这些存储(进程)。使用GenServer可以轻松处理那些受监视的消息。

本章的最后部分讨论了为什么该实现的效果不是很好[强调我的]:

回到我们的handle_cast/2实现中,您可以看到注册表既链接又监视着存储桶:

{:ok, bucket} = KV.Bucket.start_link([])
ref = Process.monitor(bucket)

这是个坏主意,因为我们不希望注册表在存储桶崩溃时崩溃。正确的解决方法是实际上不将存储桶链接到注册表。相反,我们会将每个存储段链接到一种称为“管理程序”的特殊类型的过程,该过程专门用于处理故障和崩溃。我们将在下一章中详细了解它们。

管理程序太无聊了,您甚至可能根本不需要编写一个管理程序模块并在其中use Supervisor。只需在Application模块中定义主管即可为您提供完美的服务。这是我其中一个应用程序的应用程序模块中的start/2函数:

  def start(_type, _args) do
    children = [
      # Start the Ecto repository
      MyApp.Repo,
      # Start the Telemetry supervisor
      MyAppWeb.Telemetry,
      # Start the PubSub system
      {Phoenix.PubSub, name: MyApp.PubSub},
      # Start the Endpoint (http/https)
      MyAppWeb.Endpoint,
      # Start a worker by calling: MyApp.Worker.start_link(arg)
      # {MyApp.Worker, arg}
      {DynamicSupervisor, strategy: :one_for_one, name: :agent_supervisor}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end

您可以看到我的应用的Ecto存储库具有自己的流程,其他一些标准组件也是如此,例如TelemetryPhoenix.PubSub和我的应用程序的Web终结点。这些流程本身都是监督者,甚至可能是相当密集的流程“树”。

我还使用标准:agent_supervisor添加了一个名为DynamicSupervisor的动态主管。它的想法是管理任意数量(即动态数量)的代理。 GenServer模块/进程也可以使用相同的命令。如果我决定(出于某种原因)向该动态管理器添加一些逻辑,则可以在其中创建一个MyApp.AgentSupervisor模块,use DynamicSupervisor,然后在我的应用程序中更改行(“子规范”) start/2功能:

{DynamicSupervisor, strategy: :one_for_one, name: :agent_supervisor}

至:

MyApp.AgentSupervisor

有趣的是,在一个专业案例中,我用use GenServer实施了一个“混乱”的主管,因为我需要 所涵盖的实际监督“策略”通过基本的Supervisor功能。