Понимание стратегий перезапуска процесса: переходные, временные и постоянные
В современном строительстве решающее значение имеют программные системы, способные корректно обрабатывать сбои и поддерживать бесперебойную работу. Elixir, мощный и отказоустойчивый язык программирования, предлагает ряд стратегий управления процессами, когда они сталкиваются с проблемами. Эти стратегии перезапуска процессов, включая :permanent, :temporary и :transient, играют ключевую роль в обеспечении надежности и отказоустойчивости системы. В этом руководстве мы рассмотрим концепции и лучшие практики, лежащие в основе этих стратегий перезапуска, предоставив вам знания для разработки надежных программных систем в Elixir.
Когда какой вариант вы используете?
Есть три варианта :restart:
1. Используйте :permanent, когда:
- Этот процесс имеет решающее значение для общей работы системы, и его сбой может серьезно повлиять на функциональность системы.
- Вы хотите, чтобы процесс автоматически перезапускался при невозможности поддерживать доступность системы.
- Примеры могут включать подключения к базе данных, основные компоненты или критически важные службы.
2. Используйте :temporary, когда:
- Этот процесс не является существенным для непрерывной работы системы, и его отказ можно допустить без существенных нарушений.
- Вы хотите свести к минимуму автоматические перезапуски некритических процессов, чтобы избежать чрезмерного использования ресурсов.
- Примеры могут включать второстепенные фоновые задачи, средства ведения журнала или сборщики метрик.
3. Используйте :transient, когда:
- Процесс не является критическим сам по себе, но является частью группы процессов или подсистемы, где важна согласованность между процессами.
- Вы хотите убедиться, что зависимые процессы перезапускаются вместе с временным процессом, чтобы сохранить общую целостность системы.
- Примеры могут включать рабочие процессы в системе обработки заданий или компоненты распределенной системы.
Эта опция позволяет настроить поведение GenServer при восстановлении в случае сбоя, обеспечивая больший контроль над системой. Таким образом, выбор стратегии перезапуска зависит от критичности процесса для вашей системы и его зависимостей. Тщательное рассмотрение этих факторов поможет вам спроектировать надежные и отказоустойчивые системы, которые смогут эффективно восстанавливаться после сбоев, избегая при этом ненужных перезапусков некритических компонентов.
Использование опции :restart
Чтобы использовать стратегии перезапуска процесса в Elixir, вам обычно приходится работать с деревом контроля, которое представляет собой иерархическую структуру, используемую для управления процессами и контроля над ними. Вот как вы можете использовать различные стратегии перезапуска (:permanent, :temporary и :transient):
1. Создание Supervisor:
- Сначала вам необходимо создать супервизора с помощью модуля
Supervisor. Вы можете использоватьSupervisor.start_link/2илиSupervisor.child_spec/2для настройки супервизора.
2. Добавление дочерних процессов:
- Затем вы добавляете дочерние процессы (которые могут включать процессы
GenServer) в дерево надзора супервизора с помощью функцииSupervisor.child_spec/2. В дочерней спецификации вы можете указать стратегию:restart.
3. Определение стратегии перезапуска:
- В дочерней спецификации вы указываете стратегию
:restart. Вы можете установить для него значение:permanent,:temporaryили:transient, в зависимости от того, как вы хотите, чтобы процесс перезапускался в случае сбоя.
Вот пример кода того, как можно настроить супервизор с различными стратегиями перезапуска:
defmodule Dummy.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
# Starts a worker by calling: Dummy.Worker.start_link(arg)
%{
id: Dummy.Permanent,
start: {Dummy.Permanent, :start_link, [[]]},
restart: :permanent,
type: :worker
},
%{
id: Dummy.Temporary,
start: {Dummy.Temporary, :start_link, [[]]},
restart: :temporary,
type: :worker
},
%{
id: Dummy.Transient,
start: {Dummy.Transient, :start_link, [[]]},
restart: :transient,
type: :worker
}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [
strategy: :one_for_one,
name: Dummy.Supervisor
]
Supervisor.start_link(children, opts)
end
end
defmodule Dummy.Permanent do
use GenServer
def start_link(_) do
GenServer.start_link(__MODULE__, :ok)
end
def init(:ok) do
{:ok, :initial_state}
end
end
defmodule Dummy.Temporary do
use GenServer
def start_link(_) do
GenServer.start_link(__MODULE__, :ok)
end
def init(:ok) do
{:ok, :initial_state}
end
end
defmodule Dummy.Transient do
use GenServer
def start_link(_) do
GenServer.start_link(__MODULE__, :ok)
end
def init(:ok) do
{:ok, :initial_state}
end
end
Давайте запустим IEx и запустим приложение.
$ iex -S mix
Если вы посмотрите, мы проверяем количество детей в Dummy.Supervisor. Воспитатель вздрогнул, и дети тоже. Получение pid и канала для Process.exit/2 с причиной :kill. Проверьте Dummy.Supervisor еще раз. Единственный отдых детей-наставников – Permanent и Transient.
$ iex -S mix
iex> Supervisor.count_children(Dummy.Supervisor)
%{active: 3, workers: 3, supervisors: 0, specs: 3}
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.129.0>, :worker, [Dummy.Permanent]},
{Dummy.Temporary, #PID<0.128.0>, :worker, [Dummy.Temporary]},
{Dummy.Transient, #PID<0.127.0>, :worker, [Dummy.Transient]}
]
iex> pid("0.128.0") |> Process.exit(:kill)
true
iex> Supervisor.count_children(Dummy.Supervisor)
%{active: 2, workers: 2, supervisors: 0, specs: 2}
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.129.0>, :worker, [Dummy.Permanent]},
{Dummy.Transient, #PID<0.127.0>, :worker, [Dummy.Transient]}
]
Если я воспользуюсь любой причиной, чтобы остановить Transient процесс, он снова вернется живым. Но если причина в :shutdown или {:shutdown, term}, процесс не возобновится, и в этой ситуации мы можем перезапустить процесс вручную, используя Supervisor.restart_child/2. А если я убью временный и попытаюсь перезапустить процесс вручную, то получу кортеж с {:error, :not_found}. Здесь важно то, что я использовал child ID.
iex> Supervisor.count_children(Dummy.Supervisor)
%{active: 3, workers: 3, supervisors: 0, specs: 3}
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.129.0>, :worker, [Dummy.Permanent]},
{Dummy.Temporary, #PID<0.128.0>, :worker, [Dummy.Temporary]},
{Dummy.Transient, #PID<0.127.0>, :worker, [Dummy.Transient]}
]
iex> pid("0.127.0") |> Process.exit(:kill)
true
iex> Supervisor.count_children(Dummy.Supervisor)
%{active: 3, workers: 3, supervisors: 0, specs: 3}
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.129.0>, :worker, [Dummy.Permanent]},
{Dummy.Temporary, #PID<0.128.0>, :worker, [Dummy.Temporary]},
{Dummy.Transient, #PID<0.145.0>, :worker, [Dummy.Transient]}
]
iex> pid("0.145.0") |> Process.exit(:normal)
true
iex> Supervisor.count_children(Dummy.Supervisor)
%{active: 3, workers: 3, supervisors: 0, specs: 3}
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.129.0>, :worker, [Dummy.Permanent]},
{Dummy.Temporary, #PID<0.128.0>, :worker, [Dummy.Temporary]},
{Dummy.Transient, #PID<0.145.0>, :worker, [Dummy.Transient]}
]
iex> pid("0.145.0") |> Process.exit(:shutdown)
true
iex> Supervisor.count_children(Dummy.Supervisor)
%{active: 2, workers: 3, supervisors: 0, specs: 3}
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.129.0>, :worker, [Dummy.Permanent]},
{Dummy.Temporary, #PID<0.128.0>, :worker, [Dummy.Temporary]},
{Dummy.Transient, :undefined, :worker, [Dummy.Transient]}
]
iex> Supervisor.restart_child(Dummy.Supervisor, Dummy.Transient)
{:ok, #PID<0.146.0>}
iex> Supervisor.count_children(Dummy.Supervisor)
%{active: 3, workers: 3, supervisors: 0, specs: 3}
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.129.0>, :worker, [Dummy.Permanent]},
{Dummy.Temporary, #PID<0.128.0>, :worker, [Dummy.Temporary]},
{Dummy.Transient, #PID<0.146.0>, :worker, [Dummy.Transient]}
]
iex> pid("0.128.0") |> Process.exit(:shutdown)
true
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.129.0>, :worker, [Dummy.Permanent]},
{Dummy.Transient, #PID<0.146.0>, :worker, [Dummy.Transient]}
]
iex> Supervisor.restart_child(Dummy.Supervisor, Dummy.Temporary)
{:error, :not_found}
И последнее, и не менее важное: когда я играю с Permanent и пытаюсь убить по любой причине, этот процесс всегда возвращается живым.
iex> Supervisor.count_children(Dummy.Supervisor)
%{active: 3, workers: 3, supervisors: 0, specs: 3}
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.129.0>, :worker, [Dummy.Permanent]},
{Dummy.Temporary, #PID<0.128.0>, :worker, [Dummy.Temporary]},
{Dummy.Transient, #PID<0.127.0>, :worker, [Dummy.Transient]}
]
iex> pid("0.129.0") |> Process.exit(:shutdown)
true
iex> Supervisor.which_children(Dummy.Supervisor)
[
{Dummy.Permanent, #PID<0.145.0>, :worker, [Dummy.Permanent]},
{Dummy.Temporary, #PID<0.128.0>, :worker, [Dummy.Temporary]},
{Dummy.Transient, #PID<0.127.0>, :worker, [Dummy.Transient]}
]
Заключение
В заключение, стратегии перезапуска процессов в Elixir являются незаменимыми инструментами для создания программных систем, которые могут предоставлять бесперебойные услуги. Применяя такие стратегии, как :permanent, :temporary и :transient. Эти стратегии позволяют нам создавать отказоустойчивые системы, которые корректно восстанавливаются после сбоев, обеспечивая более плавную и надежную работу для конечных пользователей. Продолжая исследовать мир Elixir, сохраняйте эти стратегии перезапуска в своем наборе инструментов.