當前位置: 首頁>>代碼示例 >>用法及示例精選 >>正文


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。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。