檢視原始碼 DynamicSupervisor 行為 (Elixir v1.16.2)

最佳化僅動態啟動子項目的監督程式。

Supervisor 模組設計用於處理大多數靜態子項目,這些子項目會在監督程式啟動時以指定順序啟動。DynamicSupervisor 啟動時沒有子項目。相反地,子項目會透過 start_child/2 依需求啟動,且子項目之間沒有順序。這讓 DynamicSupervisor 能夠使用有效率的資料結構容納數百萬個子項目,並同時執行某些作業,例如關閉。

範例

動態監督程式啟動時沒有子項目,而且通常會有名稱

children = [
  {DynamicSupervisor, name: MyApp.DynamicSupervisor, strategy: :one_for_one}
]

Supervisor.start_link(children, strategy: :one_for_one)

子項目規格中提供的選項記載於 start_link/1 中。

動態監督程式執行後,我們可以使用它依需求啟動子項目。假設有這個範例 GenServer

defmodule Counter do
  use GenServer

  def start_link(initial) do
    GenServer.start_link(__MODULE__, initial)
  end

  def inc(pid) do
    GenServer.call(pid, :inc)
  end

  def init(initial) do
    {:ok, initial}
  end

  def handle_call(:inc, _, count) do
    {:reply, count, count + 1}
  end
end

我們可以使用 start_child/2 搭配子項目規格來啟動 Counter 伺服器

{:ok, counter1} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Counter, 0})
Counter.inc(counter1)
#=> 0

{:ok, counter2} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Counter, 10})
Counter.inc(counter2)
#=> 10

DynamicSupervisor.count_children(MyApp.DynamicSupervisor)
#=> %{active: 2, specs: 2, supervisors: 0, workers: 2}

可擴充性和分割

DynamicSupervisor 是負責啟動其他程序的單一程序。在某些應用程式中, DynamicSupervisor 可能會成為瓶頸。為了解決這個問題,您可以啟動 DynamicSupervisor 的多個執行個體,然後選擇一個「隨機」執行個體來啟動子執行個體。

取代

children = [
  {DynamicSupervisor, name: MyApp.DynamicSupervisor}
]

DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Counter, 0})

您可以執行下列動作

children = [
  {PartitionSupervisor,
   child_spec: DynamicSupervisor,
   name: MyApp.DynamicSupervisors}
]

然後

DynamicSupervisor.start_child(
  {:via, PartitionSupervisor, {MyApp.DynamicSupervisors, self()}},
  {Counter, 0}
)

在上述程式碼中,我們啟動一個分割區監督程式,它會預設為您的機器中的每個核心啟動一個動態監督程式。然後,您不是透過名稱呼叫 DynamicSupervisor,而是透過分割區監督程式呼叫它,並使用 self() 作為路由金鑰。這表示每個程序會指派一個現有的動態監督程式。請閱讀 PartitionSupervisor 文件以取得更多資訊。

基於模組的監督程式

Supervisor 類似,動態監督程式也支援基於模組的監督程式。

defmodule MyApp.DynamicSupervisor do
  # Automatically defines child_spec/1
  use DynamicSupervisor

  def start_link(init_arg) do
    DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end
end

請參閱 Supervisor 文件,以了解何時可能想要使用基於模組的監督程式。緊接在 use DynamicSupervisor 之前的 @doc 註解會附加到產生的 child_spec/1 函式。

use DynamicSupervisor

當您 use DynamicSupervisor 時, DynamicSupervisor 模組會設定 @behaviour DynamicSupervisor 並定義 child_spec/1 函式,因此您的模組可以用作監督樹中的子執行個體。

名稱註冊

監督程式與 GenServer 繫結到相同的名稱註冊規則。請在 GenServer 的文件中進一步了解這些規則。

從 Supervisor 的 :simple_one_for_one 進行移轉

如果您使用 Supervisor 模組中已棄用的 :simple_one_for_one 策略,您可以透過幾個步驟移轉到 DynamicSupervisor

想像給定的「舊」程式碼

defmodule MySupervisor do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  def start_child(foo, bar, baz) do
    # This will start child by calling MyWorker.start_link(init_arg, foo, bar, baz)
    Supervisor.start_child(__MODULE__, [foo, bar, baz])
  end

  @impl true
  def init(init_arg) do
    children = [
      # Or the deprecated: worker(MyWorker, [init_arg])
      %{id: MyWorker, start: {MyWorker, :start_link, [init_arg]}}
    ]

    Supervisor.init(children, strategy: :simple_one_for_one)
  end
end

它可以像這樣升級到 DynamicSupervisor

defmodule MySupervisor do
  use DynamicSupervisor

  def start_link(init_arg) do
    DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  def start_child(foo, bar, baz) do
    # If MyWorker is not using the new child specs, we need to pass a map:
    # spec = %{id: MyWorker, start: {MyWorker, :start_link, [foo, bar, baz]}}
    spec = {MyWorker, foo: foo, bar: bar, baz: baz}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

  @impl true
  def init(init_arg) do
    DynamicSupervisor.init(
      strategy: :one_for_one,
      extra_arguments: [init_arg]
    )
  end
end

DynamicSupervisor 的不同之處在於,它會在呼叫 start_child/2 的時候預期子規格,而不是在 init 回呼中。如果在初始化時給予任何初始引數,例如 [initial_arg],它可以在 DynamicSupervisor.init/1 上的 :extra_arguments 旗標中給予。

摘要

類型

提供給 start_linkinit/1 函式的選項

start_child 函式的回傳值

提供給 start_link 函式的選項

支援的策略

在 init 時回傳的監督旗標

回呼

呼叫回呼以啟動監督程式,以及在熱程式碼升級期間。

函式

回傳一個規格,以在監督程式下啟動動態監督程式。

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

接收一組 options,用於初始化動態監督程式。

動態將子規格新增到 supervisor 並啟動該子項。

使用給定的選項啟動監督程式。

使用給定的 moduleinit_arg 啟動基於模組的監督程式程序。

使用給定的 reason 同步停止給定的監督程式。

終止由 pid 識別的給定子項。

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

類型

@type init_option() ::
  {:strategy, strategy()}
  | {:max_restarts, non_neg_integer()}
  | {:max_seconds, pos_integer()}
  | {:max_children, non_neg_integer() | :infinity}
  | {:extra_arguments, [term()]}

提供給 start_linkinit/1 函式的選項

@type on_start_child() ::
  {:ok, pid()}
  | {:ok, pid(), info :: term()}
  | :ignore
  | {:error, {:already_started, pid()} | :max_children | term()}

start_child 函式的回傳值

@type option() :: GenServer.option()

提供給 start_link 函式的選項

@type strategy() :: :one_for_one

支援的策略

@type sup_flags() :: %{
  strategy: strategy(),
  intensity: non_neg_integer(),
  period: pos_integer(),
  max_children: non_neg_integer() | :infinity,
  extra_arguments: [term()]
}

在 init 時回傳的監督旗標

回呼

@callback init(init_arg :: term()) :: {:ok, sup_flags()} | :ignore

呼叫回呼以啟動監督程式,以及在熱程式碼升級期間。

開發人員通常會在 init 回呼的結尾呼叫 DynamicSupervisor.init/1 以傳回適當的監控旗標。

函式

連結到此函式

child_spec(options)

檢視原始碼 (自 1.6.1 起)

回傳一個規格,以在監督程式下啟動動態監督程式。

它接受與 start_link/1 相同的選項。

有關子項目的詳細資訊,請參閱 Supervisor

連結到此函式

count_children(supervisor)

檢視原始碼 (自 1.6.0 起)
@spec count_children(Supervisor.supervisor()) :: %{
  specs: non_neg_integer(),
  active: non_neg_integer(),
  supervisors: non_neg_integer(),
  workers: non_neg_integer()
}

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

此映射包含下列金鑰

  • :specs - 子程序的數量

  • :active - 由此監控器管理的所有正在執行中的子程序計數

  • :supervisors - 所有監控器的計數,無論子程序是否仍存在

  • :workers - 所有工作程序的計數,無論子程序是否仍存在

連結到此函式

init(options)

檢視原始碼 (自 1.6.0 起)
@spec init([init_option()]) :: {:ok, sup_flags()}

接收一組 options,用於初始化動態監督程式。

這通常會在模組化監控器的 init/1 回呼的結尾呼叫。有關詳細資訊,請參閱模組文件中的「模組化監控器」區段。

它接受與 start_link/1 相同的 options:name 除外),並傳回包含監控器選項的組元。

範例

def init(_arg) do
  DynamicSupervisor.init(max_children: 1000)
end
連結到此函式

start_child(supervisor, child_spec)

檢視原始碼 (自 1.6.0 起)
@spec start_child(
  Supervisor.supervisor(),
  Supervisor.child_spec()
  | {module(), term()}
  | module()
  | (old_erlang_child_spec :: :supervisor.child_spec())
) :: on_start_child()

動態將子規格新增到 supervisor 並啟動該子項。

child_spec 應為有效的子項規格,詳見 Supervisor 文件中的「子項規格」區段。子項程序將按照子項規格中定義的內容啟動。請注意,儘管規格中仍然需要 :id 欄位,但值會被忽略,因此不需要是唯一的。

如果子項程序啟動函數傳回 {:ok, child}{:ok, child, info},則子項規格和 PID 會新增到監控器,而此函數也會傳回相同的值。

如果子項程序啟動函數傳回 :ignore,則不會將任何子項新增到監控樹,而此函數也會傳回 :ignore

如果子項程序啟動函數傳回錯誤組或錯誤值,或如果它失敗,則會捨棄子項規格,而此函數也會傳回 {:error, error},其中 error 是子項程序啟動函數傳回的錯誤或錯誤值,或如果它失敗,則為失敗原因。

如果監控器已經有 N 個子項,而 N 超過監控器初始化時設定的 :max_children 數量(請參閱 init/1),則此函數會傳回 {:error, :max_children}

連結到此函式

start_link(options)

檢視原始碼 (自 1.6.0 起)
@spec start_link([option() | init_option()]) :: Supervisor.on_start()

使用給定的選項啟動監督程式。

此函數通常不會直接呼叫,而是當使用 DynamicSupervisor 作為另一個監控器的子項時呼叫

children = [
  {DynamicSupervisor, name: MySupervisor}
]

如果監控器成功產生,則此函數會傳回 {:ok, pid},其中 pid 是監控器的 PID。如果監控器已命名,且已存在具有指定名稱的程序,則函數會傳回 {:error, {:already_started, pid}},其中 pid 是該程序的 PID。

請注意,使用此函數啟動的監控器會連結到父程序,且不僅會在發生崩潰時結束,也會在父程序以 :normal 原因結束時結束。

選項

  • :name - 在給定的名稱下註冊監督者。支援的值在 GenServer 模組文件中的「名稱註冊」區段中說明。

  • :strategy - 重新啟動策略選項。唯一支援的值是 :one_for_one,這表示如果子程序終止,不會終止其他子程序。您可以在 Supervisor 模組文件中進一步了解策略。

  • :max_restarts - 在時間範圍內允許的最大重新啟動次數。預設為 3

  • :max_seconds - :max_restarts 套用的時間範圍。預設為 5

  • :max_children - 在此監督者下同時執行的最大子程序數量。當超過 :max_children 時,start_child/2 會傳回 {:error, :max_children}。預設為 :infinity

  • :extra_arguments - 傳遞給 start_child/2 的子程序規格中指定參數之前附加的參數。預設為空清單。

連結到此函式

start_link(module, init_arg, opts \\ [])

檢視原始碼 (自 1.6.0 起)
@spec start_link(module(), term(), [option()]) :: Supervisor.on_start()

使用給定的 moduleinit_arg 啟動基於模組的監督程式程序。

要啟動監督者,init/1 回呼會在給定的 module 中呼叫,並以 init_arg 作為其參數。init/1 回呼必須傳回監督者規格,可以使用 init/1 函數建立。

如果 init/1 回呼傳回 :ignore,此函數也會傳回 :ignore,而監督者會以原因 :normal 終止。如果它失敗或傳回不正確的值,此函數會傳回 {:error, term},其中 term 是包含錯誤資訊的術語,而監督者會以原因 term 終止。

:name 選項也可以在註冊監督者名稱時提供,支援的值在 GenServer 模組文件中的「名稱註冊」區段中說明。

如果監控器成功產生,則此函數會傳回 {:ok, pid},其中 pid 是監控器的 PID。如果監控器已命名,且已存在具有指定名稱的程序,則函數會傳回 {:error, {:already_started, pid}},其中 pid 是該程序的 PID。

請注意,使用此函數啟動的監控器會連結到父程序,且不僅會在發生崩潰時結束,也會在父程序以 :normal 原因結束時結束。

連結到此函式

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

檢視原始碼 (自 1.7.0 起)
@spec stop(Supervisor.supervisor(), reason :: term(), timeout()) :: :ok

使用給定的 reason 同步停止給定的監督程式。

如果監督程式終止於給定的原因,則回傳 :ok。如果終止於其他原因,則呼叫會結束。

此函數會保留 OTP 語意,關於錯誤回報。如果原因是任何除了 :normal:shutdown{:shutdown, _} 以外的原因,則會記錄一個錯誤回報。

連結到此函式

terminate_child(supervisor, pid)

檢視原始碼 (自 1.6.0 起)
@spec terminate_child(Supervisor.supervisor(), pid()) :: :ok | {:error, :not_found}

終止由 pid 識別的給定子項。

如果成功,此函數會回傳 :ok。如果沒有具有給定 PID 的程序,則此函數會回傳 {:error, :not_found}

連結到此函式

which_children(supervisor)

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

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

請注意,在低記憶體條件下監督大量子程序時呼叫此函數可能會導致記憶體不足例外狀況。

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

  • id - 對於動態監督程式,它總是 :undefined

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

  • type - :worker:supervisor,如子規格中定義

  • modules - 如子規格中定義