Понимание стратегий перезапуска процесса: переходные, временные и постоянные
В современном строительстве решающее значение имеют программные системы, способные корректно обрабатывать сбои и поддерживать бесперебойную работу. 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.Superviso
r еще раз. Единственный отдых детей-наставников – 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, сохраняйте эти стратегии перезапуска в своем наборе инструментов.