管理和应用
本章是Mix和OTP指南的一部分,它取决于本指南的前几章。有关更多信息,请阅读简介指南或查看边栏中的章节索引。
到目前为止,我们的应用程序有一个注册表,可以监视数十个桶,甚至数百个桶。虽然我们认为迄今为止我们的实施非常好,但没有任何软件是无缺陷的,并且失败肯定会发生。
当事情失败时,你的第一反应可能是:“让我们解救这些错误”。但是在Elixir中,我们避免了拯救例外的防守编程习惯。相反,我们说“让它崩溃”。如果有一个导致我们的注册表崩溃的错误,我们没有什么可担心的,因为我们将设置一个将启动注册表的新副本的主管。
在本章中,我们将学习主管以及应用程序。我们不会创建一个,而是两个主管,并用它们来监督我们的流程。
我们的第一个主管
创建管理员与创建GenServer没有多大区别。我们将在文件中定义一个名为KV.Supervisor
,将使用Supervisor行为的模块lib/kv/supervisor.ex
:
defmodule KV.Supervisor do use Supervisor def start_link(opts) do Supervisor.start_link(__MODULE__, :ok, opts) end def init(:ok) do children = [ KV.Registry ] Supervisor.init(children, strategy: :one_for_one) end end
我们的主管迄今只有一个孩子:KV.Registry
。在我们定义了一份儿童名单后,我们呼吁Supervisor.init/2
,通过儿童和监督策略。
监督策略决定了当其中一个孩子坠毁时会发生什么。:one_for_one
意味着如果一个孩子死了,那将是唯一一个重新启动的。既然我们现在只有一个孩子,那就是我们所需要的。大Supervisor
行为支持许多不同的策略,我们将在本章中讨论它们。
一旦主管启动,它将遍历子列表,它将调用child_spec/1
每个模块的功能。我们child_spec/1
在start_supervised(KV.Bucket)
没有定义模块的情况下进行调用时听说过代理章节中的功能。
child_spec/1
函数返回描述如何启动进程的子规范,如果进程是工作者或主管,如果进程是临时的,暂时的或永久的等等。该child_spec/1
功能自动定义时,我们use Agent
,use GenServer
,use Supervisor
等我们把它与终端一试iex -S mix
:
iex(1)> KV.Registry.child_spec([]) %{ id: KV.Registry, restart: :permanent, shutdown: 5000, start: {KV.Registry, :start_link, [[]]}, type: :worker }
随着我们继续推进本指南,我们将学习这些细节。如果您希望提前浏览,请查看Supervisor文档。
在主管检索所有子规格后,它会按照它们定义的顺序逐一启动其子:start
项,并使用子规范中的子项中的信息。对于我们目前的规范,它会调用KV.Registry.start_link([])
。
到目前为止start_link/1
,总是收到一个空白的选项列表。现在是时候改变这一点了。
命名过程
虽然我们的应用程序会有多个存储桶,但它只会有一个注册表。因此,不要总是传递注册表PID,我们可以给注册表一个名称,并且始终使用它的名称来引用它。
另外,请记住,根据用户输入动态启动存储区,这意味着我们不应该使用原子名称来管理我们的存储区。但是注册表处于相反的情况,我们希望在我们的应用程序启动时启动一个注册表,最好在主管下。
所以让我们来做。让我们稍微改变我们的子类定义为元组列表而不是原子列表:
def init(:ok) do children = [ {KV.Registry, name: KV.Registry} ] Supervisor.init(children, strategy: :one_for_one) end
现在的区别在于,KV.Registry.start_link([])
主管不会调用,而是调用给主管KV.Registry.start_link([name: KV.Registry])
。如果您重新访问KV.Registry.start_link/1
实施,您会记得它只是将选项传递给GenServer
def start_link(opts) do GenServer.start_link(__MODULE__, :ok, opts) end
它将使用给定的名称注册进程。
让我们来尝试一下iex -S mix
:
iex> KV.Supervisor.start_link([]) {:ok, #PID<0.66.0>} iex> KV.Registry.create(KV.Registry, "shopping") :ok iex> KV.Registry.lookup(KV.Registry, "shopping") {:ok, #PID<0.70.0>}
当我们启动监督员时,注册表自动以给定名称启动,允许我们创建桶而无需手动启动它。
实际上,我们很少手动启动应用程序主管。相反,它是作为应用程序回调的一部分启动的。
了解应用程序
我们一直在整个应用程序中工作。每次我们更改文件并运行时mix compile
,我们都可以Generated kv app
在编辑输出中看到一条消息。
我们可以在找到生成的.app
文件_build/dev/lib/kv/ebin/kv.app
。我们来看看它的内容:
{application,kv, [{registered,[]}, {description,"kv"}, {applications,[kernel,stdlib,elixir,logger]}, {vsn,"0.0.1"}, {modules,['Elixir.KV','Elixir.KV.Bucket', 'Elixir.KV.Registry','Elixir.KV.Supervisor']}]}.
该文件包含Erlang术语(使用Erlang语法编写)。尽管我们对Erlang并不熟悉,但很容易猜到这个文件包含我们的应用程序定义。它包含了我们的应用程序version
通过它定义,所有的模块,以及我们赖以生存,像Erlang的应用程序的列表kernel
,elixir
它本身,而logger
这是在指定的:extra_applications
列表中mix.exs
。
每次我们为我们的应用程序添加一个新模块时手动更新这个文件都会很无聊。这就是Mix为我们生成和维护它的原因。
我们还可以配置产生.app
通过定制由返回的值文件application/0
我们内部mix.exs
项目文件。我们将尽快进行我们的第一次定制。
启动应用程序
当我们定义一个.app
文件(应用程序规范)时,我们可以作为一个整体来启动和停止应用程序。我们目前并不担心这个,原因有两个:
- Mix会自动为我们启动当前的应用程序。
2. 即使Mix没有为我们启动我们的应用程序,我们的应用程序在启动时还没有做任何事情
无论如何,让我们看看Mix如何为我们开始申请。让我们开始一个项目控制台,iex -S mix
并尝试:
iex> Application.start(:kv) {:error, {:already_started, :kv}}
哎呀,它已经开始了。混合通常会启动我们项目mix.exs
文件中定义的应用程序的整个层次结构,并且如果它们依赖于其他应用程序,则对所有依赖项执行相同操作。
我们可以将选项传递给Mix以要求它不启动我们的应用程序。让我们尝试运行iex -S mix run --no-start
:
iex> Application.start(:kv) :ok
我们可以停止我们的:kv
应用程序以及:logger
默认使用Elixir启动的应用程序:
iex> Application.stop(:kv) :ok iex> Application.stop(:logger) :ok
让我们尝试重新启动应用程序:
iex> Application.start(:kv) {:error, {:not_started, :logger}}
现在我们得到一个错误,因为:kv
依赖于(:logger
在这种情况下)的应用程序没有启动。我们需要以正确的顺序手动启动每个应用程序,或者Application.ensure_all_started
按以下方式调用:
iex> Application.ensure_all_started(:kv) {:ok, [:logger, :kv]}
没有什么令人兴奋的事情发生,但它显示了我们如何控制我们的应用
运行时
iex -S mix
,相当于运行iex -S mix run
。因此,无论何时在启动IEx时需要将更多选项传递给Mix,都需要键入iex -S mix run
,然后传递run
命令接受的任何选项。你可以run
通过mix help run
在shell中运行来找到更多的信息。
应用程序回调
由于我们一直都在讨论如何启动和停止应用程序,因此在应用程序启动时必须有一种方法来执行有用的操作。确实有!
我们可以指定一个应用程序回调函数。这是一个将在应用程序启动时调用的函数。该函数必须返回结果{:ok,pid}
,其中pid
是主管进程的进程标识符。
我们可以分两步配置应用程序回调。首先,打开mix.exs
文件并更改def application
为以下内容:
def application do [ extra_applications: [:logger], mod: {KV, []} ] end
该:mod
选项指定“应用程序回调模块”,然后指定在应用程序启动时传递的参数。应用程序回调模块可以是任何实现应用程序行为的模块。
现在我们已经指定KV
了模块回调,我们需要更改KV
模块,定义lib/kv.ex
如下:
defmodule KV do use Application def start(_type, _args) do KV.Supervisor.start_link(name: KV.Supervisor) end end
当我们use Application
,我们需要定义一些功能,类似于我们使用Supervisor
或者GenServer
。这次我们只需要定义一个start/2
函数。如果我们想在应用程序停止时指定自定义行为,我们可以定义一个stop/1
函数。
让我们再次启动我们的项目控制台iex -S mix
。我们将看到一个名为KV.Registry
已经运行的进程:
iex> KV.Registry.create(KV.Registry, "shopping") :ok iex> KV.Registry.lookup(KV.Registry, "shopping") {:ok, #PID<0.88.0>}
我们如何知道这是行得通的?毕竟,我们正在创建这个桶然后查找它; 当然它应该工作,对吧?那么,请记住这些KV.Registry.create/2
用法GenServer.cast/2
,因此:ok
无论消息是否找到其目标,都将返回。那时,我们不知道管理员和服务器是否启动,以及是否创建了存储桶。但是,KV.Registry.lookup/2
使用GenServer.call/3
并将阻止并等待来自服务器的响应。我们确实得到了积极的回应,所以我们知道一切都已启动并正在运行。
对于实验,请尝试重新实现KV.Registry.create/2
以GenServer.call/3
代替使用,并暂时禁用应用程序回调。再次在控制台上运行上面的代码,您将看到创建步骤立即失败。
不要忘记在恢复本教程之前将代码恢复正常!
项目还是应用?
混合区分项目和应用程序。根据我们mix.exs
文件的内容,我们会说我们有一个定义:kv
应用程序的Mix项目。我们将在后面的章节中看到,有些项目没有定义任何应用程序。
当我们说“项目”时,你应该考虑Mix。Mix是管理你的项目的工具。它知道如何编译你的项目,测试你的项目等等。它也知道如何编译和启动与您的项目相关的应用程序。
当我们谈论应用程序时,我们讨论OTP。应用程序是运行时作为整体启动和停止的实体。您可以在应用程序模块的文档中了解更多关于应用程序以及它们与引导和关闭整个系统的关系。
接下来让我们了解一种特殊类型的主管,专门用于动态启动和关闭儿童,称为简单的一对一。