当前位置: 首页>>代码示例 >>用法及示例精选 >>正文


Elixir Registry用法及代码示例


Elixir语言中 Registry 相关用法介绍如下。

本地、分散和可扩展的键值过程存储。

它允许开发人员使用给定的键查找一个或多个进程。如果注册表有 :unique 键,则键指向 0 或 1 个进程。如果注册表允许:duplicate 键,则单个键可以指向任意数量的进程。在这两种情况下,不同的 key 可以识别相同的进程。

注册表中的每个条目都与已注册 key 的进程相关联。如果进程崩溃,与该进程关联的键会自动删除。注册表中的所有键比较都是使用匹配操作 ( ===/2 ) 完成的。

注册表可用于不同目的,例如名称查找(使用 :via 选项)、存储属性、自定义调度规则或 pubsub 实现。我们将在下面探讨其中的一些用例。

注册表也可以是透明分区的,这为在具有数千或数百万条目的高度并发环境中运行注册表提供了更具可扩展性的行为。

:via 中使用

一旦使用 Registry.start_link/1 以给定名称启动注册表,它就可以使用 {:via, Registry, {registry, key}} 元组来注册和访问命名进程:

{:ok, _} = Registry.start_link(keys: :unique, name: Registry.ViaTest)
name = {:via, Registry, {Registry.ViaTest, "agent"}}
{:ok, _} = Agent.start_link(fn -> 0 end, name: name)
Agent.get(name, & &1)
#=> 0
Agent.update(name, &(&1 + 1))
Agent.get(name, & &1)
#=> 1

在前面的示例中,我们对将值关联到流程不感兴趣:

Registry.lookup(Registry.ViaTest, "agent")
#=> [{self(), nil}]

但是,在某些情况下,可能需要使用备用 {:via, Registry, {registry, key, value}} 元组将值关联到进程:

{:ok, _} = Registry.start_link(keys: :unique, name: Registry.ViaTest)
name = {:via, Registry, {Registry.ViaTest, "agent", :hello}}
{:ok, _} = Agent.start_link(fn -> 0 end, name: name)
Registry.lookup(Registry.ViaTest, "agent")
#=> [{self(), :hello}]

至此,我们一直使用 start_link/1 开始 Registry 。通常,注册表是作为监督树的一部分启动的:

{Registry, keys: :unique, name: Registry.ViaTest}

:via 中只能使用具有唯一键的注册表。如果名称已被占用,case-specific start_link 函数(上例中的 Agent.start_link/2 )将返回 {:error, {:already_started, current_pid}}

用作调度员

Registry 有一个调度机制,允许开发人员实现从调用者触发的自定义调度逻辑。例如,假设我们有一个重复的注册表,如下所示:

{:ok, _} = Registry.start_link(keys: :duplicate, name: Registry.DispatcherTest)

通过调用 register/3 ,不同的进程可以在给定键下注册并关联该键下的任何值。在这种情况下,让我们在 "hello" 键下注册当前进程并将 {IO, :inspect} 元组附加到它:

{:ok, _} = Registry.register(Registry.DispatcherTest, "hello", {IO, :inspect})

现在,有兴趣为给定键调度事件的实体可以调用 dispatch/3 并传入键和回调。此回调将使用在请求的键下注册的所有值的列表以及注册每个值的进程的 PID 以{pid, value} 元组的形式调用。在我们的示例中,value 将是上面代码中的 {module, function} 元组:

Registry.dispatch(Registry.DispatcherTest, "hello", fn entries ->
  for {pid, {module, function}} <- entries, do: apply(module, function, [pid])
end)
# Prints #PID<...> where the PID is for the process that called register/3 above
#=> :ok

调度发生在调用 dispatch/3 的进程中,在多个分区的情况下(通过派生的任务)串行或同时调用。已注册的进程不参与调度,除非明确地涉及它们(例如,通过在回调中向它们发送消息)。

此外,如果调度失败,由于注册错误,调度总是会失败,注册的进程不会被通知。因此,让我们确保至少包装并报告这些错误:

require Logger

Registry.dispatch(Registry.DispatcherTest, "hello", fn entries ->
  for {pid, {module, function}} <- entries do
    try do
      apply(module, function, [pid])
    catch
      kind, reason ->
        formatted = Exception.format(kind, reason, __STACKTRACE__)
        Logger.error("Registry.dispatch/3 failed with #{formatted}")
    end
  end
end)
# Prints #PID<...>
#=> :ok

您还可以通过显式发送消息来替换整个apply 系统。这就是我们接下来会看到的例子。

用作PubSub

注册表也可用于通过依赖 dispatch/3 函数来实现本地的、非分布式的、可扩展的 PubSub,类似于上一节:但是,在这种情况下,我们将向每个关联的进程发送消息,而不是调用给定的module-function。

在本例中,我们还将分区数设置为在线调度程序的数量,这将使注册表在高并发环境下的性能更高:

{:ok, _} =
  Registry.start_link(
    keys: :duplicate,
    name: Registry.PubSubTest,
    partitions: System.schedulers_online()
  )

{:ok, _} = Registry.register(Registry.PubSubTest, "hello", [])

Registry.dispatch(Registry.PubSubTest, "hello", fn entries ->
  for {pid, _} <- entries, do: send(pid, {:broadcast, "world"})
end)
#=> :ok

上面的示例将消息 {:broadcast, "world"} 广播到在 "topic" (或到目前为止我们称之为 "key")"hello" 下注册的所有进程。

register/3 的第三个参数是与当前进程关联的值。虽然在上一节中我们在调度时使用了它,但在这个特定的示例中我们对它不感兴趣,所以我们将它设置为一个空列表。如有必要,您可以存储更有意义的值。

注册

查找、调度和注册是高效且即时的,但会以延迟取消订阅为代价。例如,如果一个进程崩溃,它的键会自动从注册表中删除,但更改可能不会立即传播。这意味着某些操作可能会返回已经死亡的进程。当这种情况发生时,将在函数文档中明确说明。

但是,请记住,这些情况通常不是问题。毕竟,PID 引用的进程可能随时崩溃,包括从注册表获取值和向其发送消息之间。标准库的许多部分都是为了解决这个问题而设计的,例如 Process.monitor/1 如果受监视的进程已经死了,它将立即传递 :DOWN 消息,而 Kernel.send/2 则充当死进程的 no-op。

ETS

请注意,注册表使用一个 ETS 表和每个分区的两个 ETS 表。

相关用法


注:本文由纯净天空筛选整理自elixir-lang.org大神的英文原创作品 Registry。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。