Elixir語言中 Agent
相關用法介紹如下。
代理是圍繞狀態的簡單抽象。
通常在 Elixir 中需要共享或存儲必須從不同進程或同一進程在不同時間點訪問的狀態。
模塊提供了一個基本的服務器實現,允許通過一個簡單的 API 檢索和更新狀態。Agent
例子
例如,以下代理實現了一個計數器:
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 之間的隔離(類似於
s)。特別是,作為參數傳遞給GenServer
函數調用的函數在代理(服務器)內部被調用。這種區別很重要,因為您可能希望避免在代理內部進行昂貴的操作,因為它們會有效地阻塞代理,直到請求得到滿足。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
時,它會自動定義一個
函數,允許我們直接在主管下啟動代理。要在初始計數器為 0 的主管下啟動代理,可以執行以下操作:child_spec/1
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
有關更多詳細信息,請參閱
模塊中的"Child specification" 部分。緊接在Supervisor
use Agent
之前的@doc
注釋將附加到生成的
函數。child_spec/1
名稱注冊
代理綁定到與 GenServer 相同的名稱注冊規則。在
文檔中閱讀更多相關信息。GenServer
關於分布式代理的一句話
考慮分布式代理的局限性很重要。代理提供了兩種 API,一種用於匿名函數,另一種需要顯式模塊、函數和參數。
在具有多個節點的分布式設置中,接受匿名函數的 API 僅在調用者(客戶端)和代理具有相同版本的調用者模塊時才有效。
請記住,在使用代理執行 "rolling upgrades" 時也會出現此問題。滾動升級是指以下情況:您希望通過shutting down
部署您的一些節點的新版本軟件,並用運行新版本軟件的節點替換它們。在此設置中,您的環境的一部分將具有給定模塊的一個版本,而另一部分將具有同一模塊的另一個版本(較新的版本)。
最好的解決方案是在使用分布式代理時簡單地使用顯式模塊、函數和參數 API。
熱代碼交換
代理可以通過簡單地將模塊、函數和參數元組傳遞給更新指令來使其代碼實時熱交換。例如,假設您有一個名為 :sample
的代理,並且您希望將其內部狀態從關鍵字列表轉換為Map。可以通過以下指令完成:
{:update, :sample, {:advanced, {Enum, :into, [%{}]}}}
代理的狀態將作為第一個參數添加到給定的參數列表 ([%{}]
) 中。
相關用法
- Elixir Agent.get_and_update用法及代碼示例
- Elixir Agent.get用法及代碼示例
- Elixir Agent.cast用法及代碼示例
- Elixir Agent.start_link用法及代碼示例
- Elixir Agent.update用法及代碼示例
- Elixir Agent.stop用法及代碼示例
- Elixir Agent.start用法及代碼示例
- Elixir Application.get_env用法及代碼示例
- Elixir Application用法及代碼示例
- Elixir Access.filter用法及代碼示例
- Elixir Access.all用法及代碼示例
- Elixir Atom用法及代碼示例
- Elixir Atom.to_string用法及代碼示例
- Elixir Access.key用法及代碼示例
- Elixir Access.get用法及代碼示例
- Elixir Access.key!用法及代碼示例
- Elixir Atom.to_charlist用法及代碼示例
- Elixir Access.fetch用法及代碼示例
- Elixir Access.at!用法及代碼示例
- Elixir Access.at用法及代碼示例
- Elixir Access用法及代碼示例
- Elixir Access.fetch!用法及代碼示例
- Elixir Application.ensure_started用法及代碼示例
- Elixir Access.elem用法及代碼示例
- Elixir Access.pop用法及代碼示例
注:本文由純淨天空篩選整理自elixir-lang.org大神的英文原創作品 Agent。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。