Что такое 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 — стратегия перезапуска, определяющая, как поступать при сбое дочернего процесса.
Стратегии перезапуска
-
:one_for_one
При сбое одного дочернего процесса — перезапускается только он. -
:one_for_all
Если падает один — перезапускаются все дочерние процессы. -
:rest_for_one
При сбое одного процесса — перезапускается он и все процессы, запущенные после него. -
: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, позволяющий изолировать ошибки, управлять зависимостями и поддерживать надёжность приложений с минимальным количеством ручного контроля.