OTP Супервизоры
Супервизоры это специальные процессы с единственной целью: мониторинг других процессов. Супервизоры позволяют нам создавать отказоустойчивые приложения при помощи автоматического перезапуска дочерних процессов в случае их отказа.
Настройка
Магия супервизоров заключена в функции Supervisor.start_link/2
. Помимо запуска нашего супервизора и его потомков, она позволяет нам определять стратегию, которую будет использовать наш супервизор для управления дочерними процессами.
Давайте начнём, используя SimpleQueue
из урока OTP Concurrency:
Создадим новый проект используяmix new simple_queue --sup
для генерации проекта с деревом супервизора. Код для модуля SimpleQueue
должен находиться в lib/simple_queue.ex
, а код супервизора, который мы добавим, пойдет в lib/simple_queue/application.ex
.
Дочерние процессы определяются с помощью списка двумя способами. Либо списком названий модулей:
defmodule SimpleQueue.Application do
use Application
def start(_type, _args) do
children = [
SimpleQueue
]
opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
Supervisor.start_link(children, opts)
end
end
Либо списком кортежей, если вы хотите добавить параметры конфигурации:
defmodule SimpleQueue.Application do
use Application
def start(_type, _args) do
children = [
{SimpleQueue, [1, 2, 3]}
]
opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
Supervisor.start_link(children, opts)
end
end
Если мы выполним iex -S mix
, то увидим, что SimpleQueue
автоматически запустился:
iex> SimpleQueue.queue
[1, 2, 3]
Если наш SimpleQueue
процесс упадёт или завершится, супервизор автоматически перезапустит его как ни в чём не бывало.
Стратегии перезапуска
На данный момент для Супервизора доступны три стратегии перезапуска:
-
:one_for_one
- Перезапуск только упавшего дочернего процесса. -
:one_for_all
- Перезапуск всех дочерних процессов из события падения. -
:rest_for_one
- Перезапуск упавшего процесса и всех процессов, запущенных после него.
Спецификация дочерних процессов
После запуска супервизор должен знать, как запускать/останавливать/перезапускать свои дочерние процессы. Каждый модуль дочернего процесса должен иметь функцию child_spec/1
для определения этих поведений. Макросы use GenServer
, use Supervisor
, и use Agent
автоматически определяют этот метод для нас (SimpleQueue
имеет use GenServer
, поэтому нам не надо вносить правки в этот модуль), но если вам необходимо определить его вручную, child_spec/1
должен вернуть ассоциативный массив параметров:
def child_spec(opts) do
%{
id: SimpleQueue,
start: {__MODULE__, :start_link, [opts]},
shutdown: 5_000,
restart: :permanent,
type: :worker
}
end
-
id
- Обязательный ключ. Используется супервизором для определения спецификации дочернего процесса. -
start
- Обязательный ключ. Модуль/Функция/Аргументы для вызова, когда процесс запускается супервизором. -
shutdown
- Дополнительный ключ. Определяет поведение при остановке. Варианты:-
:brutal_kill
- Дочерний процесс останавливается незамедлительно - любое положительное число - время в миллисекундах, которое супервизор будет ждать перед убийством дочернего процесса.
-
Если процесс имеет тип :worker
, время по умолчанию будет равно 5000.
-
:infinity
- Супервизор будет бесконечно ждать завершения дочернего процесса. По умолчанию для типа процесса:supervisor
. Не рекомендуется для типа:worker
. -
restart
- Дополнительный ключ. Существует несколько подходов для обработки падений дочерних процессов:-
:permanent
- Дочерний процесс всегда перезапускается.
-
По умолчанию для всех процессов
-
:temporary
- Дочерний процесс никогда не перезапускается. -
:transient
- Дочерний процесс перезапускается только в случае аномального завершения. -
type
- Дополнительный ключ. Процессы могут быть либо:worker
, либо:supervisor
. По умолчанию:worker
.
Динамичный Супервизор
Супервизор обычно запускаются вместе со списком дочерних процессов во время запуска приложения. Однако, иногда дочерние процессы остаются неизвестны во время запуска нашего приложения (Например, мы можем иметь веб-приложение, которое запускает новые процессы для обработки подключений пользователей к нашему сайту). В этих случаях нам нужен супервизор, где дочерние процессы могут быть запущены по требованию. Решением является Динамичный Супервизор (DynamicSupervisor).
Поскольку мы не будем указывать дочерние процессы, нам нужно только определить параметры времени выполнения для супервизора.
Динамичный Супервизор поддерживает только стратегию :one_for_one
:
options = [
name: SimpleQueue.Supervisor,
strategy: :one_for_one
]
DynamicSupervisor.start_link(options)
Затем, чтобы запустить новый SimpleQueue динамически, мы используем start_child/2
, которая берет на вход супервизор и модуль дочернего процесса с определенной спецификацией (повторюсь, т.к. SimpleQueue
используют use GenServer
, то спецификация дочернего процесса уже определена):
{:ok, pid} = DynamicSupervisor.start_child(SimpleQueue.Supervisor, SimpleQueue)
Наблюдатель задач
Для задач есть специальный супервизор — Task.Supervisor
. Он разработан для динамически создаваемых задач и под капотом использует DynamicSupervisor
.
Настройка
Включение Task.Supervisor
ничем не отличается от других супервизоров:
children = [
{Task.Supervisor, name: ExampleApp.TaskSupervisor, restart: :transient}
]
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
Основное различие между Supervisor
и Task.Supervisor
в параметре перезапуска по умолчанию - :temporary
(задачи никогда не будут перезапущены).
Наблюдаемые задачи
При запущенном супервизоре мы можем использовать функцию start_child/2
для создания наблюдаемой задачи:
{:ok, pid} = Task.Supervisor.start_child(ExampleApp.TaskSupervisor, fn -> background_work end)
Если наша задача преждевременно выйдет из строя, она будет перезапущена. Это может быть особенно полезно при работе со входящими соединениями и обработке фоновых задач.
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!