【问题标题】:Can an Elixir OTP Supervisor just "use Supervisor" or does it have to "use Genserver"?Elixir OTP 主管可以只“使用主管”还是必须“使用 Genserver”?
【发布时间】:2017-09-19 06:20:38
【问题描述】:

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

elixir 文档 (http://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html#supervision-trees) 似乎建议您只需使用“使用 Supervisor”宏即可构建监督树。

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

我认为我需要一些正确方向的指针,也许是要查看的代码或更清晰的示例。

【问题讨论】:

  • 你指的是哪个例子?该页面没有单个use GenServer
  • 为明确的@Dogbert 编辑了问题,你说得对,该页面不使用 GenServer,但很多互联网示例都使用,所以我对如何使监督树在没有 GenServer 的情况下工作感到困惑或者如果这是可能的。
  • 如果您使用的是GenServer,则不应使用Supervisor,因为它们使用相同的回调(init 等)。 SupervisorGenserver 非常不同:你通常做的就是让监督者尽可能简单(尽可能少的逻辑),因为他们将监控基于 GenServer 的模块。当您需要动态监督树时,您将使用 simple_one_for_one 监督者,这反过来将生成基于 GenServer 的模块作为它们的子模块。如果您想举例说明您想要的东西,我们将更容易描述可能的方法。

标签: elixir erlang-otp erlang-supervisor gen-server


【解决方案1】:

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

首先,将监管者的概念与他们监管的流程相结合。即 GenServers、Agents、Tasks、GenFSMs 等。

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

  • 添加新的 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找到更多信息

【讨论】:

    【解决方案2】:

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

    我认为您在 Elixir Mix 和 OTP 指南中提到的那一章令人困惑的是,大多数示例都是针对示例项目的,该示例项目有效地最终实现了 Supervisor 更常用(也更容易)用于的一些内容.

    主管可能最清楚地理解为特殊的GenServerSupervisor 本身调用了几个 GenServer 函数:

    GenServer 是“通用服务器”。 Supervisor 类似于一个特殊的服务器,它(几乎总是)监管其他服务器(包括其他监管者)。

    但由于监管者是特殊的,他们通常只关心监管其他“通用”服务器,它们本身并不封装任何其他逻辑。

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

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

    为了确保工作人员在(何时)崩溃时重新启动,并同样确保注册表在崩溃时重新启动,您需要使用主管来执行此操作。

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

    The second chapter tho 涵盖了在分布式系统中管理状态,例如同时。它使用Agent,它基本上是一个具有一些状态的进程,可以检索和更新。

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

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

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

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

    回到我们的handle_cast/2 实现,您可以看到注册表正在链接和监控存储桶:

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

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

    主管太无聊了,你甚至可能不需要在其中编写主管模块和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 端点。而这些流程本身都是监督者,甚至可能是相当密集的流程“树”。

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

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

    只是:

    MyApp.AgentSupervisor
    

    有趣的是,在一个专业的例子中,我用use GenServer 实现了一个“混乱”的主管因为我需要的实际监督“策略”没有包含在基本的Supervisor 功能。

    【讨论】:

      猜你喜欢
      • 2016-01-16
      • 1970-01-01
      • 2016-04-20
      • 2010-12-01
      • 2016-06-23
      • 2014-07-10
      • 2016-03-20
      • 2011-02-26
      • 2017-02-21
      相关资源
      最近更新 更多