Spec-Zone.ru › Elixir 1.14

DynamicSupervisor поведение

Надзорный процесс, оптимизированный для динамического запуска дочерних процессов.

Модуль Supervisor был разработан для управления преимущественно статическими дочерними процессами, которые запускаются в заданном порядке при запуске надзорного процесса. DynamicSupervisor запускается без дочерних процессов. Вместо этого, дочерние процессы запускаются по требованию через start_child/2, и нет порядка между ними. Это позволяет DynamicSupervisor поддерживать миллионы дочерних процессов с помощью эффективных структур данных и выполнять определённые операции, такие как завершение, параллельно.

Примеры

Динамический надзорный процесс запускается без дочерних процессов и часто с именем:

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

Supervisor.start_link(children, strategy: :one_for_one)

Параметры, указанные в описании дочернего процесса, документированы в start_link/1.

После запуска динамического надзорного процесса, мы можем запустить дочерние процессы с помощью start_child/2, который получает описание дочернего процесса:

{:ok, agent1} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Agent, fn -> %{} end})
Agent.update(agent1, &Map.put(&1, :key, "value"))
Agent.get(agent1, & &1)
#=> %{key: "value"}

{:ok, agent2} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Agent, fn -> %{} end})
Agent.get(agent2, & &1)
#=> %{}

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, {Agent, fn -> %{} end})

Можно сделать так:

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

а затем:

DynamicSupervisor.start_child(
  {:via, PartitionSupervisor, {MyApp.DynamicSupervisors, self()}},
  {Agent, fn -> %{} end}
)

В данном коде мы запускаем надзорный процесс разбиения, который по умолчанию запустит динамический надзорный процесс для каждого ядра вашего компьютера. Затем, вместо вызова 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, чтобы понять, когда следует использовать модульные надзорные процессы. Аннотация @doc непосредственно перед use DynamicSupervisor будет прикреплена к сгенерированной функции child_spec/1.

Регистрация имени

Надзорный процесс подчиняется тем же правилам регистрации имени, что и GenServer. Подробнее об этих правилах см. в документации по GenServer.

Миграция из :simple_one_for_one

Если вы использовали устаревшую стратегию :simple_one_for_one из модуля Supervisor, вы можете перейти к 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, а не во время обратного вызова инициализации. Если при инициализации передаются какие-либо начальные аргументы, такие как [initial_arg], их можно указать в флаге :extra_arguments функции DynamicSupervisor.init/1.

Обзор

Типы

init_option()

Параметры, передаваемые функциям start_link и init/1

on_start_child()

Возвращаемые значения функций start_child

option()

Параметры, передаваемые функциям start_link

strategy()

Поддерживаемые стратегии

sup_flags()

Флаги надзорного процесса, возвращаемые при инициализации

Обратные вызовы

init(init_arg)

Обратный вызов, вызываемый для запуска надзорного процесса и во время горячих обновлений кода.

Функции

child_spec(opts)

Возвращает описание дочернего процесса для запуска в надзорном процессе.

count_children(supervisor)

Возвращает карту с количеством дочерних процессов для данного надзорного процесса.

init(options)

Получает набор options для инициализации динамического надзорного процесса.

start_child(supervisor, child_spec)

Динамически добавляет описание дочернего процесса к supervisor и запускает этот дочерний процесс.

start_link(options)

Запускает надзорный процесс с заданными параметрами.

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

Запускает модульный надзорный процесс с заданными module и init_arg.

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

Синхронно останавливает заданный надзорный процесс с заданным reason.

terminate_child(supervisor, pid)

Прерывает заданный дочерний процесс, идентифицированный pid.

which_children(supervisor)

Возвращает список с информацией обо всех дочерних процессах.

Типы

init_option()Source

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

Параметры, передаваемые функциям start_link и init/1

on_start_child()Source

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

Возвращаемые значения функций start_child

option()Source

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

Параметры, передаваемые функциям start_link

strategy()Source

@type strategy() :: :one_for_one

Поддерживаемые стратегии

sup_flags()Source

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

Флаги надзорного процесса, возвращаемые при инициализации

Обратные вызовы

init(init_arg)Source

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

Обратный вызов, вызываемый для запуска надзорного процесса и во время горячих обновлений кода.

Разработчики обычно вызывают DynamicSupervisor.init/1 в конце своего обратного вызова инициализации, чтобы вернуть соответствующие флаги надзора.

Функции

child_spec(opts)Source

Возвращает спецификацию для запуска динамического надзирателя под надзирателем.

См. Supervisor.

count_children(supervisor)Source

@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)Source

@spec init([init_option()]) :: {:ok, sup_flags()}

Принимает набор options, который инициализирует динамический надзиратель.

Обычно вызывается в конце обратного вызова init/1 надзирателей на основе модулей. См. раздел "Надзиратели на основе модулей" в документации модуля для получения дополнительной информации.

Принимает те же options параметры, что и start_link/1 (за исключением :name) и возвращает кортеж, содержащий параметры надзирателя.

Примеры

def init(_arg) do
  DynamicSupervisor.init(max_children: 1000)
end

start_child(supervisor, child_spec)Source

@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)Source

@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 \\ [])Source

@spec start_link(module(), term(), [option()]) :: Supervisor.on_start()

Запускает процесс надзирателя на основе модуля с указанным module и init_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)Source

@spec stop(Supervisor.supervisor(), reason :: term(), timeout()) :: :ok

Синхронно останавливает указанный надзиратель с заданной reason.

Возвращает :ok , если надзиратель завершается с заданной причиной. Если он завершается по другой причине, вызов завершается.

Эта функция поддерживает семантику OTP в отношении отчётов об ошибках. Если причина отличается от :normal, :shutdown или {:shutdown, _}, выводится отчёт об ошибке.

terminate_child(supervisor, pid)Source

@spec terminate_child(Supervisor.supervisor(), pid()) :: :ok | {:error, :not_found}

Завершает указанного дочернего процесса, идентифицируемого по pid.

При успешном выполнении функция возвращает :ok. Если процесса с указанным PID нет, функция возвращает {:error, :not_found}.

which_children(supervisor)Source

@spec which_children(Supervisor.supervisor()) :: [
  {:undefined, pid() | :restarting, :worker | :supervisor,
   [module()] | :dynamic}
]

Возвращает список с информацией обо всех дочерних процессах.

Обратите внимание, что вызов этой функции при надзоре за большим количеством дочерних процессов в условиях низкой памяти может привести к исключению из-за недостатка памяти.

Функция возвращает список кортежей, содержащих:

  • id - всегда :undefined для динамических надзирателей

  • child - PID соответствующего дочернего процесса или атом :restarting, если процесс должен быть перезапущен

  • type - :worker или :supervisor , как определено в спецификации дочернего процесса

  • modules - как определено в спецификации дочернего процесса

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.14.1/DynamicSupervisor.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API