DynamicSupervisor поведение
Наблюдатель, оптимизированный для динамического запуска дочерних процессов.
Модуль 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, чтобы узнать, когда может потребоваться использование наблюдателей, основанных на модулях. Аннотация @doc непосредственно перед use DynamicSupervisor будет прикреплена к сгенерированной функции child_spec/1.
use DynamicSupervisorПри
use DynamicSupervisor, модульDynamicSupervisorустановит@behaviour DynamicSupervisorи определит функциюchild_spec/1, чтобы ваш модуль мог использоваться как дочерний процесс в дереве наблюдения.
Регистрация имён
Наблюдатель подчиняется тем же правилам регистрации имен, что и GenServer. Дополнительную информацию об этих правилах можно найти в документации по GenServer.
Миграция из Supervisor's :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, а не при вызове init callback. Если при инициализации переданы какие-либо начальные аргументы, такие как [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(options)
Возвращает спецификацию для запуска динамического наблюдателя под наблюдателем.
- 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 в конце своего обработчика init, чтобы вернуть правильные флаги наблюдения.
child_spec(options)Source
Возвращает спецификацию для запуска динамического надзирателя под надзирателем.
Принимает те же опции, что и start_link/1.
См. 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.15.4/DynamicSupervisor.html