Наблюдатель поведение
Модуль поведения для реализации функциональности наблюдения.
Наблюдатель — это процесс, который контролирует другие процессы, которые мы называем дочерними процессами. Наблюдатели используются для построения иерархической структуры процессов, называемой деревом наблюдения. Деревья наблюдения — хороший способ структурировать отказоустойчивые приложения.
Наблюдатель, реализованный с помощью этого модуля, имеет стандартный набор функций интерфейса и включает функциональность для отслеживания и обработки ошибок. Он также вписывается в дерево наблюдения.
Примеры
Для определения наблюдателя нам сначала нужно определить дочерний процесс, который будет контролироваться. Для этого мы определим GenServer, представляющий стек:
defmodule Stack do
use GenServer
def start_link(state, opts \\ []) do
GenServer.start_link(__MODULE__, state, opts)
end
def handle_call(:pop, _from, [h | t]) do
{:reply, h, t}
end
def handle_cast({:push, h}, t) do
{:noreply, [h | t]}
end
end Теперь мы можем определить нашего наблюдателя и запустить его следующим образом:
# Import helpers for defining supervisors
import Supervisor.Spec
# Supervise the Stack server which will be started with
# a single argument [:hello] and the default registered
# name of MyStack.
children = [
worker(Stack, [[:hello], [name: MyStack]])
]
# Start the supervisor with our child
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one) Обратите внимание, что при запуске GenServer мы регистрируем его с именем MyStack, что позволяет нам напрямую вызывать его и получать то, что находится в стеке:
GenServer.call(MyStack, :pop)
#=> :hello
GenServer.cast(MyStack, {:push, :world})
#=> :ok
GenServer.call(MyStack, :pop)
#=> :world Однако в нашем сервере стека есть ошибка. Если мы вызовем :pop и стек пуст, он аварийно завершит работу, потому что ни одна ветвь не соответствует:
GenServer.call(:sup_stack, :pop) ** (exit) exited in: GenServer.call(MyStack, :pop, 5000)
К счастью, поскольку сервер контролируется наблюдателем, наблюдатель автоматически запустит новый, с начальным стеком [:hello]:
GenServer.call(MyStack, :pop) #=> :hello
Наблюдатели поддерживают различные стратегии; в приведенном выше примере мы выбрали :one_for_one. Кроме того, каждый наблюдатель может иметь множество рабочих процессов и наблюдателей в качестве дочерних элементов, каждый из которых с собственной конфигурацией, значениями завершения и стратегиями перезапуска.
В остальной части этой документации будут рассмотрены стратегии наблюдения; также прочитайте документацию по модулю Supervisor.Spec, чтобы узнать о спецификациях для рабочих процессов и наблюдателей.
Наблюдатели на основе модулей
В приведенном выше примере наблюдатель запускался путем передачи структуры наблюдения в start_link/2. Однако наблюдатели также могут быть созданы путем явного определения модуля наблюдения:
defmodule MyApp.Supervisor do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, [])
end
def init([]) do
children = [
worker(Stack, [[:hello]])
]
# supervise/2 is imported from Supervisor.Spec
supervise(children, strategy: :one_for_one)
end
end Вы можете использовать наблюдатель на основе модуля, если:
-
Вам нужно выполнить какое-либо конкретное действие при инициализации наблюдателя, например, настроить таблицу ETS.
-
Вы хотите выполнить частичную горячую замену кода дерева. Например, если вы добавляете или удаляете дочерние элементы, наблюдатель на основе модуля добавит и удалит новые дочерние элементы непосредственно, в то время как динамическое наблюдение требует перезапуска всего дерева для выполнения таких операций.
Стратегии
Наблюдатели поддерживают различные стратегии наблюдения (через параметр :strategy , как показано выше):
-
:one_for_one— если дочерний процесс завершается, перезапускается только этот процесс. -
:one_for_all— если дочерний процесс завершается, все остальные дочерние процессы завершаются, а затем все дочерние процессы (включая завершенный) перезапускаются. -
:rest_for_one— если дочерний процесс завершается, «остальные» дочерние процессы, то есть дочерние процессы после завершенного в порядке запуска, завершаются. Затем завершенный дочерний процесс и оставшиеся дочерние процессы перезапускаются. -
:simple_one_for_one— аналогично:one_for_one, но лучше подходит для динамического присоединения дочерних элементов. Эта стратегия требует, чтобы спецификация наблюдателя содержала только один дочерний элемент. Многие функции в этом модуле ведут себя немного иначе при использовании этой стратегии.
Простой один к одному
Наблюдатель :simple_one_for_one полезен, когда вы хотите динамически запускать и останавливать контролируемые дочерние элементы. Например, представьте, что вы хотите динамически создать несколько стеков. Мы можем сделать это, определив наблюдателя :simple_one_for_one:
# Import helpers for defining supervisors
import Supervisor.Spec
# This time, we don't pass any argument because
# the argument will be given when we start the child
children = [
worker(Stack, [], restart: :transient)
]
# Start the supervisor with our one child
{:ok, sup_pid} = Supervisor.start_link(children, strategy: :simple_one_for_one) Здесь есть несколько различий:
-
спецификация простого «один к одному» может определять только один дочерний элемент, который работает как шаблон при вызове
start_child/2 -
мы определили, что дочерний элемент имеет стратегию перезапуска
:transient. Это означает, что если дочерний процесс завершается по причине:normal,:shutdownили{:shutdown, term}, он не будет перезапущен. Это полезно, поскольку позволяет нашим рабочим процессам вежливо завершиться и быть удаленными из наблюдателя:simple_one_for_one, без перезапуска. Более подробную информацию о стратегиях перезапуска можно найти в документации по модулюSupervisor.Spec
Определив наблюдателя, давайте динамически запустим стеки:
{:ok, pid} = Supervisor.start_child(sup_pid, [[:hello, :world], []])
GenServer.call(pid, :pop) #=> :hello
GenServer.call(pid, :pop) #=> :world
{:ok, pid} = Supervisor.start_child(sup_pid, [[:something, :else], []])
GenServer.call(pid, :pop) #=> :something
GenServer.call(pid, :pop) #=> :else
Supervisor.count_children(sup_pid)
#=> %{active: 2, specs: 1, supervisors: 0, workers: 2} Причины завершения
Из приведенного выше примера вы могли заметить, что стратегия перезапуска :transient для рабочего процесса не перезапускает дочерний элемент в случае завершения с причиной :normal, :shutdown или {:shutdown, term}.
Итак, можно спросить: какую причину завершения я должен выбрать при завершении своего рабочего процесса? Есть три варианта:
-
:normal— в таких случаях выход не будет записан в журнал, нет перезапуска в переходном режиме, и связанные процессы не завершаются. -
:shutdownили{:shutdown, term}— в таких случаях выход не будет записан в журнал, нет перезапуска в переходном режиме, и связанные процессы завершаются с той же причиной, если они не обрабатывают завершения. -
любой другой термин — в таких случаях выход будет записан в журнал, есть перезапуски в переходном режиме, и связанные процессы завершаются с той же причиной, если они не обрабатывают завершения.
Регистрация имен
Наблюдатель подчиняется тем же правилам регистрации имен, что и GenServer. Подробнее об этих правилах можно узнать в документации по GenServer.
Резюме
Типы
- child()
- name()
-
Имя наблюдателя
- on_start()
-
Возвращаемые значения функций
start_link - on_start_child()
-
Возвращаемые значения функций
start_child - options()
-
Параметры, используемые функциями
start* - supervisor()
-
Ссылка на наблюдателя
Функции
- count_children(supervisor)
-
Возвращает карту, содержащую значения количества для данного наблюдателя
- delete_child(supervisor, child_id)
-
Удаляет спецификацию дочернего элемента, идентифицированную по
child_id - restart_child(supervisor, child_id)
-
Перезапускает дочерний процесс, идентифицированный по
child_id - start_child(supervisor, child_spec_or_args)
-
Динамически добавляет спецификацию дочернего элемента к
supervisorи запускает этот дочерний элемент - start_link(children, options)
-
Запускает наблюдателя с заданными дочерними элементами
- start_link(module, arg, options \\ [])
-
Запускает модуль наблюдателя с заданным
arg - stop(supervisor, reason \\ :normal, timeout \\ :infinity)
-
Останавливает заданного наблюдателя с заданной
reason - terminate_child(supervisor, pid_or_child_id)
-
Завершает заданные дочерние элементы, идентифицированные по идентификатору процесса или ID дочернего элемента
- which_children(supervisor)
-
Возвращает список с информацией обо всех дочерних элементах данного наблюдателя
Обработчики
- init(args)
-
Обработчик, вызываемый для запуска наблюдателя и во время горячих обновлений кода
Типы
child()
child() :: pid | :undefined
name()
name() :: atom | {:global, term} | {:via, module, term} Имя наблюдателя
on_start()
on_start ::
{:ok, pid} |
:ignore |
{:error, {:already_started, pid} | {:shutdown, term} | term} Возвращаемые значения функций start_link
on_start_child()
on_start_child ::
{:ok, child} |
{:ok, child, info :: term} |
{:error, {:already_started, child} | :already_present | term} Возвращаемые значения функций start_child
options()
options() :: [name: name, strategy: Supervisor.Spec.strategy, max_restarts: non_neg_integer, max_seconds: non_neg_integer]
Параметры, используемые функциями start*
supervisor()
supervisor() :: pid | name | {atom, node} Ссылка на наблюдателя
Функции
count_children(supervisor)
count_children(supervisor) :: %{specs: non_neg_integer, active: non_neg_integer, supervisors: non_neg_integer, workers: non_neg_integer} Возвращает карту, содержащую значения количества для данного наблюдателя.
Карта содержит следующие ключи:
-
:specs— общее количество дочерних элементов, мертвых или живых. -
:active— количество активно работающих дочерних процессов, управляемых этим наблюдателем. -
:supervisors— количество всех наблюдателей, живы ли эти дочерние наблюдатели или нет. -
:workers— количество всех рабочих процессов, живы ли эти дочерние рабочие процессы или нет.
delete_child(supervisor, child_id)
delete_child(supervisor, Supervisor.Spec.child_id) ::
:ok |
{:error, error} when error: :not_found | :simple_one_for_one | :running | :restarting Удаляет спецификацию дочернего элемента, идентифицированную по child_id.
Соответствующий дочерний процесс не должен выполняться; используйте terminate_child/2, чтобы завершить его, если он запущен.
При успешном выполнении эта функция возвращает :ok. Эта функция может вернуть ошибку с соответствующей кортежем ошибки, если child_id не найдена, или если текущий процесс выполняется или перезапускается.
Эта операция не поддерживается :simple_one_for_one надзирателями.
restart_child(supervisor, child_id)
restart_child(supervisor, Supervisor.Spec.child_id) ::
{:ok, child} |
{:ok, child, term} |
{:error, error} when error: :not_found | :simple_one_for_one | :running | :restarting | term Перезапускает дочерний процесс, идентифицированный по child_id.
Спецификация дочернего процесса должна существовать, и соответствующий дочерний процесс не должен выполняться.
Обратите внимание, что для временных дочерних процессов спецификация дочернего процесса автоматически удаляется при завершении дочернего процесса, и поэтому такие дочерние процессы перезапустить нельзя.
Если функция запуска дочернего процесса возвращает {:ok, child} или {:ok, child, info}, pid добавляется в надзирателя, и эта функция возвращает то же значение.
Если функция запуска дочернего процесса возвращает :ignore, pid остается установленным на :undefined, и эта функция возвращает {:ok, :undefined}.
Эта функция может вернуть ошибку с соответствующей кортежем ошибки, если child_id не найдена, или если текущий процесс выполняется или перезапускается.
Если функция запуска дочернего процесса возвращает кортеж ошибки или ошибочное значение, или если она завершается с ошибкой, эта функция возвращает {:error, error}.
Эта операция не поддерживается :simple_one_for_one надзирателями.
start_child(supervisor, child_spec_or_args)
start_child(supervisor, Supervisor.Spec.spec | [term]) :: on_start_child
Динамически добавляет спецификацию дочернего процесса в supervisor и запускает этот дочерний процесс.
child_spec должно быть допустимой спецификацией дочернего процесса (если только надзиратель не является :simple_one_for_one надзирателем, см. ниже). Дочерний процесс будет запущен в соответствии с определением в спецификации дочернего процесса.
В случае :simple_one_for_one, используется спецификация дочернего процесса, определённая в надзирателе, и вместо child_spec, ожидается произвольный список терминов. Дочерний процесс будет запущен путем добавления данного списка к существующим аргументам функции в спецификации дочернего процесса.
Если уже существует спецификация дочернего процесса с указанным идентификатором, child_spec отбрасывается, и эта функция возвращает ошибку с :already_started или :already_present соответственно, если соответствующий дочерний процесс выполняется или нет.
Если функция запуска дочернего процесса возвращает {:ok, child} или {:ok, child,
info}, то спецификация дочернего процесса и pid добавляются в надзиратель, и эта функция возвращает то же значение.
Если функция запуска дочернего процесса возвращает :ignore, спецификация дочернего процесса добавляется в надзиратель, pid устанавливается на :undefined, и эта функция возвращает {:ok, :undefined}.
Если функция запуска дочернего процесса возвращает кортеж ошибки или ошибочное значение, или если она завершается с ошибкой, спецификация дочернего процесса отбрасывается, и эта функция возвращает {:error, error}, где error — термин, содержащий информацию об ошибке и спецификации дочернего процесса.
start_link(children, options)
start_link(module, term) :: on_start
start_link([Supervisor.Spec.spec], options) :: on_start
Запускает надзирателя с заданными дочерними процессами.
Стратегия должна быть предоставлена с помощью параметра :strategy. Кроме того, параметры :max_restarts и :max_seconds могут быть настроены, как описано в документации для Supervisor.Spec.supervise/2.
Параметры также могут использоваться для регистрации имени надзирателя. Поддерживаемые значения описаны в разделе «Регистрация имени» в документации модуля GenServer.
Если надзиратель и его дочерние процессы успешно созданы (т. е. если функция запуска каждого дочернего процесса возвращает {:ok, child}, {:ok, child, info}, или :ignore), эта функция возвращает {:ok, pid}, где pid — pid надзирателя. Если процесс с указанным именем уже существует, функция возвращает {:error,
{:already_started, pid}}, где pid — pid этого процесса.
Если функция запуска любого из дочерних процессов завершается с ошибкой, возвращает кортеж ошибки или ошибочное значение, или завершается с ошибкой, надзиратель сначала завершает все уже запущенные дочерние процессы с причиной :shutdown, а затем завершает себя и возвращает {:error, {:shutdown, reason}}.
Обратите внимание, что надзиратель, запущенный с помощью этой функции, связан с родительским процессом и завершается не только при сбоях, но и если родительский процесс завершается с причиной :normal.
start_link(module, arg, options \\ [])
start_link(module, term, options) :: on_start
Запускает модуль надзирателя с заданным arg.
Для запуска надзирателя вызывается обратный вызов init/1 в заданном module, с arg в качестве аргумента. Обратный вызов init/1 должен вернуть спецификацию надзирателя, которая может быть создана с помощью функций в модуле Supervisor.Spec (особенно Supervisor.Spec.supervise/2).
Если обратный вызов init/1 возвращает :ignore, эта функция также возвращает :ignore и надзиратель завершается с причиной :normal. Если он завершается с ошибкой или возвращает некорректное значение, эта функция возвращает {:error, term}, где term — термин с информацией об ошибке, и надзиратель завершается с причиной term.
Параметр :name также может быть задан для регистрации имени надзирателя, поддерживаемые значения описаны в разделе «Регистрация имени» в документации модуля GenServer.
stop(supervisor, reason \\ :normal, timeout \\ :infinity)
stop(supervisor, reason :: term, timeout) :: :ok
Останавливает данный надзиратель с указанной reason.
Возвращает :ok, если надзиратель завершается с указанной причиной. Если он завершается с другой причиной, вызов завершается.
Эта функция сохраняет семантику OTP в отношении сообщения об ошибках. Если причина отличается от :normal, :shutdown или {:shutdown, _}, регистрируется сообщение об ошибке.
terminate_child(supervisor, pid_or_child_id)
terminate_child(supervisor, pid | Supervisor.Spec.child_id) ::
:ok |
{:error, error} when error: :not_found | :simple_one_for_one Завершает заданные дочерние процессы, идентифицированные по pid или идентификатору дочернего процесса.
Если надзиратель не является :simple_one_for_one, ожидается идентификатор дочернего процесса, и процесс, если он есть, завершается; спецификация дочернего процесса сохраняется, если только дочерний процесс не временный.
В случае :simple_one_for_one надзирателя ожидается pid. Если вместо pid задан идентификатор спецификации дочернего процесса, функция возвращает {:error, :simple_one_for_one}.
Невременной дочерний процесс может быть позже перезапущен надзирателем. Дочерний процесс также может быть перезапущен явно, вызвав restart_child/2. Используйте delete_child/2, чтобы удалить спецификацию дочернего процесса.
При успешном выполнении функция возвращает :ok. Если для данного идентификатора дочернего процесса нет спецификации дочернего процесса или нет процесса с данным pid, функция возвращает {:error, :not_found}.
which_children(supervisor)
which_children(supervisor) :: [{Supervisor.Spec.child_id | :undefined, child | :restarting, Supervisor.Spec.worker, Supervisor.Spec.modules}] Возвращает список информации обо всех дочерних процессах данного надзирателя.
Обратите внимание, что вызов этой функции при надзоре за большим количеством дочерних процессов в условиях низкой доступности памяти может вызвать исключение из-за недостатка памяти.
Функция возвращает список кортежей {id, child, type, modules}, где:
-
id- как определено в спецификации дочернего процесса или:undefinedв случаеsimple_one_for_oneнадзирателя -
child- pid соответствующего дочернего процесса,:restartingесли процесс собирается перезапуститься, или:undefinedесли такого процесса нет -
type-:workerили:supervisor, как указано в спецификации дочернего процесса -
modules- как указано в спецификации дочернего процесса
Обратные вызовы
init(args)
init(args :: term) ::
{:ok, {:supervisor.sup_flags, [Supervisor.Spec.spec]}} |
:ignore Обратный вызов, вызываемый для запуска надзирателя и во время горячих обновлений кода.
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.3.4/Supervisor.html