檢視原始碼 PartitionSupervisor (Elixir v1.16.2)
一個啟動同一個子項目的多個分割區的監督程式。
某些程序可能會成為大型系統中的瓶頸。如果這些程序可以輕易地分割其狀態,且其間沒有依賴關係,則可以使用 PartitionSupervisor
來建立多個獨立且分離的分割區。
一旦 PartitionSupervisor
啟動,就可以使用 {:via, PartitionSupervisor, {name, key}}
傳送給其子項,其中 name
是 PartitionSupervisor
的名稱,而 key 則用於路由。
此模組於 Elixir v1.14.0 中引入。
簡單範例
讓我們從一個本身沒有用的範例開始,但它顯示了分割區是如何啟動以及訊息是如何路由到它們的。
以下是一個玩具 GenServer,它只是收集給它的訊息。它會列印它們以方便說明。
defmodule Collector do
use GenServer
def start_link(args) do
GenServer.start_link(__MODULE__, args)
end
def init(args) do
IO.inspect([__MODULE__, " got args ", args, " in ", self()])
{:ok, _initial_state = []}
end
def collect(server, msg) do
GenServer.call(server, {:collect, msg})
end
def handle_call({:collect, msg}, _from, state) do
new_state = [msg | state]
IO.inspect(["current messages:", new_state, " in process", self()])
{:reply, :ok, new_state}
end
end
要執行多個這樣的伺服器,我們可以將它們放在 PartitionSupervisor
下,方法是在我們的監督樹中放置這個
{PartitionSupervisor,
child_spec: Collector.child_spec([some: :arg]),
name: MyApp.PartitionSupervisor
}
我們可以使用「via tuple」傳送訊息給它們
# The key is used to route our message to a particular instance.
key = 1
Collector.collect({:via, PartitionSupervisor, {MyApp.PartitionSupervisor, key}}, :hi)
# ["current messages:", [:hi], " in process", #PID<0.602.0>]
:ok
Collector.collect({:via, PartitionSupervisor, {MyApp.PartitionSupervisor, key}}, :ho)
# ["current messages:", [:ho, :hi], " in process", #PID<0.602.0>]
:ok
# With a different key, the message will be routed to a different instance.
key = 2
Collector.collect({:via, PartitionSupervisor, {MyApp.PartitionSupervisor, key}}, :a)
# ["current messages:", [:a], " in process", #PID<0.603.0>]
:ok
Collector.collect({:via, PartitionSupervisor, {MyApp.PartitionSupervisor, key}}, :b)
# ["current messages:", [:b, :a], " in process", #PID<0.603.0>]
:ok
現在讓我們繼續一個有用的範例。
DynamicSupervisor
範例
DynamicSupervisor
是負責啟動其他程序的單一程序。在某些應用程式中,DynamicSupervisor
可能會成為瓶頸。為了解決這個問題,你可以透過 PartitionSupervisor
啟動多個 DynamicSupervisor
執行個體,然後選擇一個「隨機」執行個體在上面啟動子項。
不要啟動單一的 DynamicSupervisor
children = [
{DynamicSupervisor, name: MyApp.DynamicSupervisor}
]
Supervisor.start_link(children, strategy: :one_for_one)
並直接在那個動態監督程式上啟動子項
DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Agent, fn -> %{} end})
你可以將動態監督程式啟動在 PartitionSupervisor
下
children = [
{PartitionSupervisor,
child_spec: DynamicSupervisor,
name: MyApp.DynamicSupervisors}
]
Supervisor.start_link(children, strategy: :one_for_one)
然後
DynamicSupervisor.start_child(
{:via, PartitionSupervisor, {MyApp.DynamicSupervisors, self()}},
{Agent, fn -> %{} end}
)
在上面的程式碼中,我們啟動一個分割監督器,它會預設為您的機器中的每個核心啟動一個動態監督器。然後,您不是透過名稱來呼叫 DynamicSupervisor
,而是透過分割監督器使用 {:via, PartitionSupervisor, {name, key}}
格式來呼叫它。我們選擇 self()
作為路由金鑰,這表示每個程序將被指定現有的動態監督器之一。請參閱 start_link/1
以查看 PartitionSupervisor
支援的所有選項。
實作注意事項
PartitionSupervisor
使用 ETS 表格或 Registry
來管理所有分割。在底層,PartitionSupervisor
為每個分割產生一個子規格,然後充當常規監督器。每個子規格的 ID 是分割號碼。
對於路由,我們使用兩種策略。如果 key
是整數,則使用 rem(abs(key), partitions)
來路由,其中 partitions
是分割數。否則,它使用 :erlang.phash2(key, partitions)
。特定的路由可能會在未來改變,因此不能依賴它。如果您想為特定金鑰擷取特定 PID,您可以使用 GenServer.whereis({:via, PartitionSupervisor, {name, key}})
。
摘要
類型
函數
@spec count_children(name()) :: %{ specs: non_neg_integer(), active: non_neg_integer(), supervisors: non_neg_integer(), workers: non_neg_integer() }
傳回包含監督器計數值的對應。
此映射包含下列金鑰
:specs
- 分割區的數量(子程序):active
- 由此監督者管理的所有積極執行的子程序計數:supervisors
- 所有監督者的計數,不論子程序是否仍然存活:workers
- 所有工作者的計數,不論子程序是否仍然存活
@spec partitions(name()) :: pos_integer()
傳回分割監督器的分割數。
@spec start_link(keyword()) :: Supervisor.on_start()
使用給定的選項啟動分割監督器。
此函式通常不會直接呼叫,而是當使用 PartitionSupervisor
作為另一個監督者的子項時呼叫
children = [
{PartitionSupervisor, child_spec: SomeChild, name: MyPartitionSupervisor}
]
如果成功產生監督者,此函式會傳回 {:ok, pid}
,其中 pid
是監督者的 PID。如果已將給定的分割區監督者名稱指定給程序,函式會傳回 {:error, {:already_started, pid}}
,其中 pid
是該程序的 PID。
請注意,使用此函式啟動的監督者會連結至父程序,且不僅會在發生故障時結束,也會在父程序以 :normal
原因結束時結束。
選項
:name
- 表示分割區監督者名稱的原子或透過元組(請參閱name/0
)。:child_spec
- 啟動分割區時要使用的子項規範。:partitions
- 包含分割區數量的正整數。預設為System.schedulers_online()
(通常為核心數)。:strategy
- 重新啟動策略選項,預設為:one_for_one
。您可以在Supervisor
模組文件了解有關策略的更多資訊。:max_restarts
- 在時間範圍內允許的最大重新啟動次數。預設為3
。:max_seconds
-:max_restarts
套用的時間範圍。預設為5
。:with_arguments
- 一個雙引數匿名函式,允許將分割區提供給子項啟動函式。請參閱下列:with_arguments
區段。
:with_arguments
有時您希望每個分區知道其分區分配的號碼。這可以使用 :with_arguments
選項來完成。此函數接收 :child_spec
選項的值和分區號碼的整數。它必須傳回一個新的參數清單,用於啟動分區程序。
例如,大多數程序都是透過呼叫 start_link(opts)
來啟動的,其中 opts
是關鍵字清單。您可以將分區注入到提供給子項目的選項中
with_arguments: fn [opts], partition ->
[Keyword.put(opts, :partition, partition)]
end
使用給定的 reason
同步停止給定的分割監督器。
如果監督程序以給定的原因終止,則傳回 :ok
。如果它以其他原因終止,呼叫就會退出。
此函數保留 OTP 語意,關於錯誤報告。如果原因是 :normal
、:shutdown
或 {:shutdown, _}
以外的其他原因,則會記錄錯誤報告。
@spec which_children(name()) :: [ {:undefined, pid() | :restarting, :worker | :supervisor, [module()] | :dynamic} ]
傳回包含所有子項目的資訊的清單。
此函數傳回包含下列元組的清單
id
- 分區號碼child
- 對應子程序的 PID 或原子:restarting
(如果程序即將重新啟動)type
-:worker
或:supervisor
,如子項目的規格中所定義modules
- 如子項目的規格中所定義