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


Elixir Agent用法及代碼示例


Elixir語言中 Agent 相關用法介紹如下。

代理是圍繞狀態的簡單抽象。

通常在 Elixir 中需要共享或存儲必須從不同進程或同一進程在不同時間點訪問的狀態。

Agent 模塊提供了一個基本的服務器實現,允許通過一個簡單的 API 檢索和更新狀態。

例子

例如,以下代理實現了一個計數器:

defmodule Counter do
  use Agent

  def start_link(initial_value) do
    Agent.start_link(fn -> initial_value end, name: __MODULE__)
  end

  def value do
    Agent.get(__MODULE__, & &1)
  end

  def increment do
    Agent.update(__MODULE__, &(&1 + 1))
  end
end

用法是:

Counter.start_link(0)
#=> {:ok, #PID<0.123.0>}

Counter.value()
#=> 0

Counter.increment()
#=> :ok

Counter.increment()
#=> :ok

Counter.value()
#=> 2

由於代理服務器進程,計數器可以安全地同時遞增。

代理提供客戶端和服務器 API 之間的隔離(類似於 GenServer s)。特別是,作為參數傳遞給 Agent 函數調用的函數在代理(服務器)內部被調用。這種區別很重要,因為您可能希望避免在代理內部進行昂貴的操作,因為它們會有效地阻塞代理,直到請求得到滿足。

考慮這兩個例子:

# Compute in the agent/server
def get_something(agent) do
  Agent.get(agent, fn state -> do_something_expensive(state) end)
end

# Compute in the agent/client
def get_something(agent) do
  Agent.get(agent, & &1) |> do_something_expensive()
end

第一個函數阻止代理。第二個函數將所有狀態複製到客戶端,然後在客戶端執行操作。需要考慮的一個方麵是數據是否足夠大以至於需要在服務器中進行處理,至少最初是這樣,還是足夠小以便宜地發送給客戶端。另一個因子是數據是否需要原子處理:獲取狀態並在代理之外調用do_something_expensive(state)意味著代理的狀態可以同時更新。這在更新的情況下特別重要,因為如果多個客戶端嘗試將相同的狀態更新為不同的值,則在客戶端而不是在服務器中計算新狀態可能會導致競爭條件。

如何監督

Agent 最常在監督樹下啟動。當我們調用 use Agent 時,它會自動定義一個 child_spec/1 函數,允許我們直接在主管下啟動代理。要在初始計數器為 0 的主管下啟動代理,可以執行以下操作:

children = [
  {Counter, 0}
]

Supervisor.start_link(children, strategy: :one_for_all)

雖然也可以簡單地將Counter 作為孩子傳遞給主管,例如:

children = [
  Counter # Same as {Counter, []}
]

Supervisor.start_link(children, strategy: :one_for_all)

上麵的定義不適用於這個特定的例子,因為它會嘗試用一個空列表的初始值來啟動計數器。但是,這在您自己的代理中可能是一個可行的選擇。一種常見的方法是使用關鍵字列表,因為這將允許設置初始值並為計數器進程命名,例如:

def start_link(opts) do
  {initial_value, opts} = Keyword.pop(opts, :initial_value, 0)
  Agent.start_link(fn -> initial_value end, opts)
end

然後您可以使用 Counter{Counter, name: :my_counter} 甚至 {Counter, initial_value: 0, name: :my_counter} 作為子規範。

use Agent 還接受一個選項列表,用於配置子規範以及它如何在主管下運行。生成的 child_spec/1 可以使用以下選項進行自定義:

  • :id - 子規範標識符,默認為當前模塊
  • :restart - 當孩子應該重新啟動時,默認為 :permanent
  • :shutdown - 如何立即關閉孩子,或者讓孩子有時間關閉

例如:

use Agent, restart: :transient, shutdown: 10_000

有關更多詳細信息,請參閱 Supervisor 模塊中的"Child specification" 部分。緊接在use Agent 之前的@doc 注釋將附加到生成的 child_spec/1 函數。

名稱注冊

代理綁定到與 GenServer 相同的名稱注冊規則。在 GenServer 文檔中閱讀更多相關信息。

關於分布式代理的一句話

考慮分布式代理的局限性很重要。代理提供了兩種 API,一種用於匿名函數,另一種需要顯式模塊、函數和參數。

在具有多個節點的分布式設置中,接受匿名函數的 API 僅在調用者(客戶端)和代理具有相同版本的調用者模塊時才有效。

請記住,在使用代理執行 "rolling upgrades" 時也會出現此問題。滾動升級是指以下情況:您希望通過shutting down 部署您的一些節點的新版本軟件,並用運行新版本軟件的節點替換它們。在此設置中,您的環境的一部分將具有給定模塊的一個版本,而另一部分將具有同一模塊的另一個版本(較新的版本)。

最好的解決方案是在使用分布式代理時簡單地使用顯式模塊、函數和參數 API。

熱代碼交換

代理可以通過簡單地將模塊、函數和參數元組傳遞給更新指令來使其代碼實時熱交換。例如,假設您有一個名為 :sample 的代理,並且您希望將其內部狀態從關鍵字列表轉換為Map。可以通過以下指令完成:

{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}

代理的狀態將作為第一個參數添加到給定的參數列表 ([%{}]) 中。

相關用法


注:本文由純淨天空篩選整理自elixir-lang.org大神的英文原創作品 Agent。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。