檢視原始碼 PartitionSupervisor (Elixir v1.16.2)

一個啟動同一個子項目的多個分割區的監督程式。

某些程序可能會成為大型系統中的瓶頸。如果這些程序可以輕易地分割其狀態,且其間沒有依賴關係,則可以使用 PartitionSupervisor 來建立多個獨立且分離的分割區。

一旦 PartitionSupervisor 啟動,就可以使用 {:via, PartitionSupervisor, {name, key}} 傳送給其子項,其中 namePartitionSupervisor 的名稱,而 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}})

摘要

函數

傳回包含監督器計數值的對應。

傳回分割監督器的分割數。

使用給定的選項啟動分割監督器。

使用給定的 reason 同步停止給定的分割監督器。

傳回包含所有子項目的資訊的清單。

類型

@type name() :: atom() | {:via, module(), term()}

PartitionSupervisor 的名稱。

函數

連結至這個函式

count_children(supervisor)

檢視原始碼 (自 1.14.0 起)
@spec count_children(name()) :: %{
  specs: non_neg_integer(),
  active: non_neg_integer(),
  supervisors: non_neg_integer(),
  workers: non_neg_integer()
}

傳回包含監督器計數值的對應。

此映射包含下列金鑰

  • :specs - 分割區的數量(子程序)

  • :active - 由此監督者管理的所有積極執行的子程序計數

  • :supervisors - 所有監督者的計數,不論子程序是否仍然存活

  • :workers - 所有工作者的計數,不論子程序是否仍然存活

連結至這個函式

partitions(name)

檢視原始碼 (自 1.14.0 起)
@spec partitions(name()) :: pos_integer()

傳回分割監督器的分割數。

連結至這個函式

start_link(opts)

檢視原始碼 (自 1.14.0 起)
@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
連結至這個函式

stop(supervisor, reason \\ :normal, timeout \\ :infinity)

檢視原始碼 (自 1.14.0 起)
@spec stop(name(), reason :: term(), timeout()) :: :ok

使用給定的 reason 同步停止給定的分割監督器。

如果監督程序以給定的原因終止,則傳回 :ok。如果它以其他原因終止,呼叫就會退出。

此函數保留 OTP 語意,關於錯誤報告。如果原因是 :normal:shutdown{:shutdown, _} 以外的其他原因,則會記錄錯誤報告。

連結至這個函式

which_children(name)

檢視原始碼 (自 1.14.0 起)
@spec which_children(name()) :: [
  {:undefined, pid() | :restarting, :worker | :supervisor,
   [module()] | :dynamic}
]

傳回包含所有子項目的資訊的清單。

此函數傳回包含下列元組的清單

  • id - 分區號碼

  • child - 對應子程序的 PID 或原子 :restarting(如果程序即將重新啟動)

  • type - :worker:supervisor,如子項目的規格中所定義

  • modules - 如子項目的規格中所定義