Что такое Supervisor и как он работает?

В Elixir Supervisor — это специальный процесс, предназначенный для наблюдения за другими процессами и автоматического перезапуска дочерних процессов при их сбое. Он является частью фреймворка OTP (Open Telecom Platform), который обеспечивает устойчивость и отказоустойчивость приложений.

Основная идея

Supervisor управляет группой дочерних процессов (workers или других supervisors), и обеспечивает механизм автоматического восстановления в случае их аварийного завершения. Такая архитектура позволяет создавать надёжные системы, в которых сбой одного компонента не приводит к сбою всего приложения.

Как работает Supervisor

Supervisor запускается как обычный процесс и получает список дочерних процессов, которые он должен запустить и контролировать. При сбое одного из дочерних процессов он принимает решение — что с ним делать: перезапустить, игнорировать или завершить всю группу.

Supervisor реализует поведение :supervisor, и используется через макрос use Supervisor.

Основные функции и шаги

1. Определение супервизора

defmodule MyApp.Supervisor do
use Supervisor
def start_link(init_arg) do
Supervisor.start_link(\__MODULE_\_, init_arg, name: \__MODULE_\_)
end
def init(\_init_arg) do
children = \[
{MyApp.Worker, arg1},
{MyApp.OtherWorker, arg2}
\]
Supervisor.init(children, strategy: :one_for_one)
end
end

Аргументы в Supervisor.init/2

  • children — список спецификаций дочерних процессов (можно передавать модули или спецификации через Supervisor.child_spec/2);

  • strategy — стратегия перезапуска, определяющая, как поступать при сбое дочернего процесса.

Стратегии перезапуска

  1. :one_for_one
    При сбое одного дочернего процесса — перезапускается только он.

  2. :one_for_all
    Если падает один — перезапускаются все дочерние процессы.

  3. :rest_for_one
    При сбое одного процесса — перезапускается он и все процессы, запущенные после него.

  4. :simple_one_for_one _(устаревшая)
    _Использовалась для однородных дочерних процессов. Заменена на DynamicSupervisor.

Формат описания дочернего процесса

Elixir позволяет указывать дочерние процессы в виде:

{ModuleName, arg}

или более явным способом:

%{
id: MyApp.Worker,
start: {MyApp.Worker, :start_link, \[arg\]},
restart: :permanent,
shutdown: 5000,
type: :worker,
modules: \[MyApp.Worker\]
}

Поля спецификации дочернего процесса

  • :id — уникальный идентификатор;

  • :start — кортеж с модулем, функцией запуска и аргументами;

  • :restart — стратегия перезапуска (:permanent, :transient, :temporary);

  • :shutdown — таймаут завершения процесса;

  • :type — :worker или :supervisor;

  • :modules — список модулей, которые реализует процесс.

Пример запуска Supervisor в приложении

В файле application.ex:

def start(\_type, \_args) do
children = \[
MyApp.Supervisor
\]
opts = \[strategy: :one_for_one, name: MyApp.Application\]
Supervisor.start_link(children, opts)
end

Контроль за падениями

Supervisor следит за завершением дочерних процессов. Если за короткий промежуток времени (по умолчанию — 5 секунд) произойдёт несколько сбоев подряд (по умолчанию 3), Supervisor сам завершит работу, передав ошибку дальше (вплоть до выхода из системы). Это поведение настраивается через опции:

Supervisor.start_link(children, strategy: :one_for_one, max_restarts: 10, max_seconds: 5)

Использование DynamicSupervisor

Когда необходимо динамически добавлять/удалять дочерние процессы во время выполнения, используется DynamicSupervisor:

DynamicSupervisor.start_child(DynamicSupervisorName, {MyWorker, arg})

Он не требует заранее фиксированного списка дочерних процессов.

Использование Supervisor.Spec (до Elixir 1.5)

Ранее использовались конструкции вида:

worker(MyModule, \[args\], restart: :transient)

Сейчас рекомендуется использовать child_spec/1 и модульные API.

Пример с GenServer

\# worker
defmodule MyWorker do
use GenServer
def start_link(init_arg) do
GenServer.start_link(\__MODULE_\_, init_arg, name: \__MODULE_\_)
end
def init(arg), do: {:ok, arg}
end
\# supervisor
defmodule MySupervisor do
use Supervisor
def start_link(\_) do
Supervisor.start_link(\__MODULE_\_, \[\], name: \__MODULE_\_)
end
def init(\_) do
children = \[
{MyWorker, "hello"}
\]
Supervisor.init(children, strategy: :one_for_one)
end
end

Supervisor — это ключевой инструмент построения устойчивых систем в Elixir, позволяющий изолировать ошибки, управлять зависимостями и поддерживать надёжность приложений с минимальным количеством ручного контроля.