Elixir OTP 主管可以 "use Supervisor" 还是必须 "use Genserver"?
Can an Elixir OTP Supervisor just "use Supervisor" or does it have to "use Genserver"?
所以看例子,有点懵:
Elixir 文档 (http://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html#supervision-trees) 似乎建议您可以仅使用 "use Supervisor" 宏构建监督树。
但我在博客文章/随机互联网搜索中看到的所有示例 "use Genserver"。那么监督树是否一定要使用Genserver接口呢?
我想我需要一些正确方向的指示,也许是要查看的代码或更清晰的示例。
在复杂的应用程序中设计监督树可能是构建 opt 应用程序最具挑战性的方面之一。但是,在大多数情况下,我发现大多数完成的应用程序都需要这种复杂性。
首先,将主管的概念与他们监督的流程相结合。即 GenServers、代理、任务、GenFSM 等
对于许多简单的 apps/web 应用程序,平面主管树将起作用。工作流程是这样的;
- 添加新的 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
上找到更多信息
要回答标题中的问题,use Supervisor
本身就足够了。
我认为你在 Elixir Mix 和 OTP 指南中提到的章节令人困惑的是,大多数示例都是针对示例项目的,这些示例项目最终有效地实现了一些 Supervisor
更典型的(并且很容易)用于。
主管可能最清楚地理解为某种特殊的东西GenServer
; Supervisor
本身调用了几个 GenServer
函数:
一个GenServer
是一个'generic server'。 Supervisor
有点像一个特殊的服务器,(几乎总是)只 监督其他服务器(包括其他监督者)。
但是因为监督者很特殊,他们通常只关心监督其他 'generic' 服务器,他们自己不封装任何其他逻辑。
您经常在许多博客文章和搜索结果的示例中看到“use GenServer
”的原因是 GenServer
更为普遍,因为所有有效的无限数量可能 'generic servers' 有人可能想要。一个监督者甚至整个监督树都比较无聊,因为它主要只是在崩溃时处理重启监督进程。
您可能有一群工人可以与之互动,例如外部服务。这些 worker 很可能会用 use GenServer
(或 use Agent
)很好地实现。您应用程序的其他一些组件需要与这些工作人员进行交互,例如给他们工作。那应该封装为自己的过程,例如作为 GenServer
(或 Registry
)。
为了确保工作人员在(当)崩溃时重新启动,并类似地确保注册表在崩溃时重新启动,您需要使用主管来这样做。
在 Elixir Mix 和 OTP 指南中,运行 示例是分布式 key-value 商店。第一章主要是 Mix 的介绍。
The second chapter 涵盖了分布式系统中的状态管理,例如同时。它使用 Agent
,这基本上是一个具有可以检索和更新的状态的进程。
The third chapter 涵盖了 GenServer
但请注意,上一章中使用 Agent
实现的基本 key-value 存储并未修改为使用 GenServer
.相反,本章涵盖构建“进程注册表”,即管理多个 key-value 存储并按名称访问它们的方法。请注意,它的大部分功能也可以用 Agent
实现(它本身建立在 GenServer
之上)。
注册中心也实际上是一个单独的key-value存储——其中的键是其他key-value 商店和值是对那些其他商店的 运行 流程的引用。
本章末尾介绍了进程监控并使用它来处理 (Agent
) key-value 存储进程崩溃的情况。如果不监视这些进程,就无法知道它们何时崩溃以及何时从注册表中删除这些存储(进程)。使用 GenServer
可以轻松处理那些受监控的消息。
本章的最后讨论了为什么该实现不是很好[强调我的]:
Returning to our handle_cast/2
implementation, you can see the registry is both linking and monitoring the buckets:
{:ok, bucket} = KV.Bucket.start_link([])
ref = Process.monitor(bucket)
This is a bad idea, as we don’t want the registry to crash when a bucket crashes. The proper fix is to actually not link the bucket to the registry. Instead, we will link each bucket to a special type of process called Supervisors, which are explicitly designed to handle failures and crashes. We will learn more about them in the next chapter.
Supervisors 太无聊了,你可能根本不需要编写一个 supervisor 模块并 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 端点。这些进程本身都是主管,甚至可能是相当密集的 'trees' 个进程。
我还使用标准 DynamicSupervisor
添加了一个名为 :agent_supervisor
的动态主管。它的想法是管理任意数量(即动态数量)的代理;同样可以用于 GenServer
modules/processes 。如果我决定(出于某种原因)向该动态主管添加一些逻辑,我可以创建一个 MyApp.AgentSupervisor
模块,在其中 use DynamicSupervisor
,并更改我的行('child spec')应用程序 start/2
功能:
{DynamicSupervisor, strategy: :one_for_one, name: :agent_supervisor}
只是:
MyApp.AgentSupervisor
有趣的是,在一个专业实例中,我用 use GenServer
实现了 'messy' 主管,因为 实际监督 'strategy' 我需要 未包含在基本 Supervisor
功能中。
所以看例子,有点懵:
Elixir 文档 (http://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html#supervision-trees) 似乎建议您可以仅使用 "use Supervisor" 宏构建监督树。
但我在博客文章/随机互联网搜索中看到的所有示例 "use Genserver"。那么监督树是否一定要使用Genserver接口呢?
我想我需要一些正确方向的指示,也许是要查看的代码或更清晰的示例。
在复杂的应用程序中设计监督树可能是构建 opt 应用程序最具挑战性的方面之一。但是,在大多数情况下,我发现大多数完成的应用程序都需要这种复杂性。
首先,将主管的概念与他们监督的流程相结合。即 GenServers、代理、任务、GenFSM 等
对于许多简单的 apps/web 应用程序,平面主管树将起作用。工作流程是这样的;
- 添加新的 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
上找到更多信息要回答标题中的问题,use Supervisor
本身就足够了。
我认为你在 Elixir Mix 和 OTP 指南中提到的章节令人困惑的是,大多数示例都是针对示例项目的,这些示例项目最终有效地实现了一些 Supervisor
更典型的(并且很容易)用于。
主管可能最清楚地理解为某种特殊的东西GenServer
; Supervisor
本身调用了几个 GenServer
函数:
一个GenServer
是一个'generic server'。 Supervisor
有点像一个特殊的服务器,(几乎总是)只 监督其他服务器(包括其他监督者)。
但是因为监督者很特殊,他们通常只关心监督其他 'generic' 服务器,他们自己不封装任何其他逻辑。
您经常在许多博客文章和搜索结果的示例中看到“use GenServer
”的原因是 GenServer
更为普遍,因为所有有效的无限数量可能 'generic servers' 有人可能想要。一个监督者甚至整个监督树都比较无聊,因为它主要只是在崩溃时处理重启监督进程。
您可能有一群工人可以与之互动,例如外部服务。这些 worker 很可能会用 use GenServer
(或 use Agent
)很好地实现。您应用程序的其他一些组件需要与这些工作人员进行交互,例如给他们工作。那应该封装为自己的过程,例如作为 GenServer
(或 Registry
)。
为了确保工作人员在(当)崩溃时重新启动,并类似地确保注册表在崩溃时重新启动,您需要使用主管来这样做。
在 Elixir Mix 和 OTP 指南中,运行 示例是分布式 key-value 商店。第一章主要是 Mix 的介绍。
The second chapter 涵盖了分布式系统中的状态管理,例如同时。它使用 Agent
,这基本上是一个具有可以检索和更新的状态的进程。
The third chapter 涵盖了 GenServer
但请注意,上一章中使用 Agent
实现的基本 key-value 存储并未修改为使用 GenServer
.相反,本章涵盖构建“进程注册表”,即管理多个 key-value 存储并按名称访问它们的方法。请注意,它的大部分功能也可以用 Agent
实现(它本身建立在 GenServer
之上)。
注册中心也实际上是一个单独的key-value存储——其中的键是其他key-value 商店和值是对那些其他商店的 运行 流程的引用。
本章末尾介绍了进程监控并使用它来处理 (Agent
) key-value 存储进程崩溃的情况。如果不监视这些进程,就无法知道它们何时崩溃以及何时从注册表中删除这些存储(进程)。使用 GenServer
可以轻松处理那些受监控的消息。
本章的最后讨论了为什么该实现不是很好[强调我的]:
Returning to our
handle_cast/2
implementation, you can see the registry is both linking and monitoring the buckets:{:ok, bucket} = KV.Bucket.start_link([]) ref = Process.monitor(bucket)
This is a bad idea, as we don’t want the registry to crash when a bucket crashes. The proper fix is to actually not link the bucket to the registry. Instead, we will link each bucket to a special type of process called Supervisors, which are explicitly designed to handle failures and crashes. We will learn more about them in the next chapter.
Supervisors 太无聊了,你可能根本不需要编写一个 supervisor 模块并 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 端点。这些进程本身都是主管,甚至可能是相当密集的 'trees' 个进程。
我还使用标准 DynamicSupervisor
添加了一个名为 :agent_supervisor
的动态主管。它的想法是管理任意数量(即动态数量)的代理;同样可以用于 GenServer
modules/processes 。如果我决定(出于某种原因)向该动态主管添加一些逻辑,我可以创建一个 MyApp.AgentSupervisor
模块,在其中 use DynamicSupervisor
,并更改我的行('child spec')应用程序 start/2
功能:
{DynamicSupervisor, strategy: :one_for_one, name: :agent_supervisor}
只是:
MyApp.AgentSupervisor
有趣的是,在一个专业实例中,我用 use GenServer
实现了 'messy' 主管,因为 实际监督 'strategy' 我需要 未包含在基本 Supervisor
功能中。