要回答标题中的问题,use Supervisor 本身就足够了。
我认为您在 Elixir Mix 和 OTP 指南中提到的那一章令人困惑的是,大多数示例都是针对示例项目的,该示例项目有效地最终实现了 Supervisor 更常用(也更容易)用于的一些内容.
主管可能最清楚地理解为特殊的GenServer; Supervisor 本身调用了几个 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 存储库有自己的进程,其他一些标准组件也是如此,例如Telemetry、Phoenix.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 功能。