Поведение супервайзора
Материал из Erlang по-русски.
| Оригинал этой статьи находится по адресу Supervisor Behaviour |
Перевод: К. Заборский
Дата: 7.11.2006
Версия: 1.1
Содержание
|
OTP design principles
Поведение супервайзора
Эта глава должна быть прочитана вместе с supervisor(3), где даны все детали поведения супервайзора.
Принципы надзора
Супервайзор отвечает за запуск, остановку и мониторинг своих дочерних процессов. Основная идея супервайзора заключается в том, что он должен поддерживать в рабочем состоянии дочерние процессы, перезапуская их в случае необходимости.
Какой дочерний процесс необходимо запустить, и за каким необходимо установить мониторинг, описывается списком спецификаций потомков. Дочерние процессы запускаются в порядке, описанном в этом списке, и останавливаются в противоположном порядке.
Пример
Модуль обратного вызова для супервайзора, запускающего сервер из главы о gen_server может выглядеть примерно так:
-module(ch_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link(ch_sup, []).
init(_Args) ->
{ok, {{one_for_one, 1, 60},
[{ch3, {ch3, start_link, []},
permanent, brutal_kill, worker, [ch3]}]}}.
one_for_one - это стратегия перезапусков.
1 и 60 определяют максимальную частоту перезапусков.
Кортеж {ch3, ...} - это спецификация потомка.
Стратегия перезапусков
one_for_one
Если завершается дочерний процесс, то только он будет перезапущен.
one_for_all
Если дочерний процесс завершается, то все дочерние процессы будут завершены, и затем все дочерние процессы, включая завершившийся вначале, будут перезапущены.
rest_for_one
Если дочерний процесс завершается, «оставшиеся» дочерние процессы – т.е. дочерние процессы, идущие после завершившегося в списке запуска процессов – будут завершены. Затем завершившийся дочерний процесс и оставшиеся перезапускаются.
Максимальная частота перезапусков
У супервайзоров существует встроенный механизм, ограничивающий число перезапусков, которые могут произойти за заданный временной интервал. Это число определяется двумя параметрами (MaxR и MaxT) в спецификации запуска, возвращаемой callback-функцией init:
init(...) ->
{ok, {{RestartStrategy, MaxR, MaxT},
[ChildSpec, ...]}}.
Если больше чем MaxR перезапусков произойдёт в течение последних MaxT секунд, супервайзор завершает все дочерние процессы, а затем завершается сам. Когда завершается супервайзор, супервайзор более высокого уровня предпринимает какое-то действие. Он или перезапускает завершившегося супервайзора, или завершается сам. Смысл такого механизма перезапуска состоит в том, чтобы предотвратить ситуацию, когда процесс погибает повторно по одной и той же причине.
Спецификации потомков
Вот определение типа спецификации потомков:
{Id, StartFunc, Restart, Shutdown, Type, Modules}
Id = term()
StartFunc = {M, F, A}
M = F = atom()
A = [term()]
Restart = permanent | transient | temporary
Shutdown = brutal_kill | integer() >=0 | infinity
Type = worker | supervisor
Modules = [Module] | dynamic
Module = atom()
- Поле Id – это имя, используемое для идентификации спецификации потомков внутри самого супервайзора.
- Поле StartFunc определяет функцию, используемую для запуска дочернего процесса. Это тупл "модуль-функция-аргументы", используемый как apply(M, F, A). Эта функция должна быть вызовом supervisor:start_link, gen_server:start_link, gen_fsm:start_link или gen_event:start_link, или иметь результатом такой вызов. (Также она может быть функцией соответствующего формата, см. подробности в supervisor(3)).
- Поле Restart определяет, когда завершившийся дочерний процесс должен быть перезапущен.
- permanent – дочерний процесс перезапускается всегда.
- temporary – дочерний процесс никогда не перезапускается.
- transient – дочерний процесс перезапускается, только если он завершается черезвычайно, т.е. причиной завершения отличающейся от normal.
- Поле Shutdown определяет, как дочерний процесс должен быть завершён.
- brutal_kill означает, что дочерний процесс завершается безусловным вызовом exit(Child, kill).
- Целочисленное значение таймаута обозначает, что супервайзор приказывает дочернему процессу завершиться, вызывая exit(Child, shutdown), и затем ожидает сигнала завершения в ответ. Если он не получает его в течение заданного времени, дочерний процесс завершается безусловно, используя exit(Child, kill).
- Если дочерний процесс также является супервайзором, это поле должно быть выставлено как infinity (бесконечность), чтобы дать поддереву процессов достаточно времени для завершения.
- Поле Type описывает, является ли дочерний процесс супервайзором или рабочим процессом.
- Поле Modules должно содержать список из одного элемента [Module], где Module – это имя модуля обратного вызова, если дочерний процесс является супервайзором, gen_server или gen_fsm. Если дочерний процесс является gen_event, поле Modules должно быть выставлено как dynamic. Эта информация используется обработчиком релизов при апгрейдах и даунгрейдах, см. управление релизами.
Пример: Спецификация потомков для запуска сервера ch3 в примере выше выглядит следующим образом:
{ch3,
{ch3, start_link, []},
permanent, brutal_kill, worker, [ch3]}
Пример: Спецификация потомков для запуска менеджера событий из главы о gen_event:
{error_man,
{gen_event, start_link, [{local, error_man}]},
permanent, 5000, worker, dynamic}
И сервер, и менеджер событий – зарегистрированные процессы, которые должны быть доступны всё время, поэтому они описаны как permanent. ch3 не нуждается в какой-либо очистке ресурсов перед завершением, поэтому время таймаута не нужно, и brutal_kill подходит для него. error_man нужно некоторое время для очистки обработчиков событий, поэтому в поле Shutdown выставлено значение в 5000 миллисекунд. Пример: спецификация потомков для запуска другого супервайзора:
{sup,
{sup, start_link, []},
transient, infinity, supervisor, [sup]}
Запуск супервайзора
В примере, приведённом выше, супервайзор запускается вызовом ch_sup:start_link():
start_link() -> supervisor:start_link(ch_sup, []).
ch_sup:start_link вызывает функцию supervisor:start_link/2. Эта функция создаёт новый процесс, и создаёт связь между ним и супервайзором.
- Первый аргумент ch_sup – имя модуля обратного вызова, т.е. модуля, где содержится функция обратного вызова init.
- Второй аргумент [] – терм, который передаётся «as-is» в функцию обратного вызова init. Здесь init не нужно входных данных, и она игнорирует аргумент. В этом случае супервайзор не регистрируется, вместо имени нужно использовать его pid. Имя может быть указано в вызове supervisor:start_link({local, Name}, Module, Args) или supervisor:start_link({global, Name}, Module, Args). Новый процесс супервайзора вызывает функцияю обратного вызова ch_sup:init([]). От init ожидается возврат вида {ok, StartSpec}:
init(_Args) ->
{ok, {{one_for_one, 1, 60},
[{ch3, {ch3, start_link, []},
permanent, brutal_kill, worker, [ch3]}]}}.
Затем супервайзор запускает все дочерние процессы в соответствии со спецификацией потомков в спецификации запуска. В этом случае имеется только один дочерний процесс ch3.
Примечание
Вызов supervisor:start_link – синхронный. Он не возвращается до того момента, пока все дочерние процессы не будут запущены.
Добавление дочерних процессов
Вдобавок к статическому дереву надзора супервайзора, мы можем также динамически добавлять дочерние процессы к существующему супервайзору при помощи следующего вызова:
supervisor:start_child(Sup, ChildSpec)
Sup представляет собой pid, или имя супервайзора. ChildSpec – спецификация потомков. Дочерние процессы, добавленные при помощи start_child/2, ведут себя таким же образом, как и другие дочерние процессы, за минусом одного важного исключения: если супервайзор завершается и создаётся заново, все дочерние процессы, которые были динамически добавлены, будут утеряны.
Остановка дочернего процесса
Любой дочерний процесс, статический или динамический, может быть остановлен согласно спецификации завершения:
supervisor:terminate_child(Sup, Id)
Спецификация потомков для остановленного дочернего процесса удаляется при помощи следующего вызова:
supervisor:delete_child(Sup, Id)
Sup представляет собой pid, или имя супервайзора. Id – идентификатор, указанный в спецификации потомков. Так же, как и с динамически добавленными дочерними процессами, эффект удаления статического дочернего процесса исчезает в случае перезапуска супервайзора.
Simple-One-For-One супервайзоры
Супервайзор со стратегией перезапусков simple_one_for_one – это упрощённый one_for_one супервайзор, в котором все дочерние процессы представляют собой динамически добавляемые экземпляры одного и того же процесса. Пример модуля обратного вызова для simple_one_for_one супервайзора:
-module(simple_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link(simple_sup, []).
init(_Args) ->
{ok, {{simple_one_for_one, 0, 1},
[{call, {call, start_link, []},
temporary, brutal_kill, worker, [call]}]}}.
При запуске супервайзор не запускает никаких дочерних процессов. Вместо этого дочерние процессы добавляются динамически при помощи:
supervisor:start_child(Sup, List)
Sup представляет собой pid или имя супервайзора. List – произвольный список термов, который будет добавлен к списку аргументов, указанных в спецификации потомков. Если функция запуска указана в виде {M, F, A}, то дочерний процесс запускается вызовом apply(M, F, A++List). К примеру, добавление потомка к simple_sup показанному выше:
supervisor:start_child(Pid, [id1])
даёт в результате дочерний процесс, запущенный вызовом apply(call, start_link, []++[id1]), или на самом деле:
call:start_link(id1)
Остановка
Т.к. супервайзор является частью дерева надзора супервайзора, он автоматически будет остановлен своим супервайзором. При получении запроса на остановку он остановит все дочерние процессы в порядке, обратном порядку запуска процессов, согласно соответствующим спецификациям завершения, а затем остановится сам.




