所以看一些例子,我有点困惑:
elixir docs(https://github.com/Azure/azure-mobile-apps-net-server)似乎建议您只使用“使用主管”宏来构建监督树。
但我在博客文章/随机互联网搜索中看到的所有示例都“使用Genserver”。那么监督树必须使用Genserver接口吗?
我想我需要一些指向正确方向的指针,可能需要查看代码或更清晰的示例。
答案 0 :(得分:2)
在复杂的应用程序中设计监督树可能是构建opt应用程序最具挑战性的方面之一。但是,在大多数情况下,我发现大多数应用程序都需要这种复杂性。
首先,将主管的概念与他们监督的流程相结合。即GenServers,代理,任务,GenFSM等。
对于许多简单的应用程序/网络应用程序,平面管理器树将起作用。工作流程是这样的;
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
(更容易)的实现。用于。
一个上司可能最清楚地被理解为类似于特殊的GenServer
; Supervisor
本身调用了几个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
存储库具有自己的流程,其他一些标准组件也是如此,例如Telemetry
,Phoenix.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
功能。