PartitionSupervisor
Надзиратель, который запускает несколько партиций одного и того же дочернего процесса.
В крупных системах некоторые процессы могут стать узкими местами. Если состояние этих процессов можно тривиально разбить на части (при отсутствии зависимостей между ними), то они могут использовать PartitionSupervisor для создания нескольких изолированных и независимых партиций.
После запуска PartitionSupervisor можно отправлять сообщения дочерним процессам, используя {:via, PartitionSupervisor, {name, key}}, где name — имя PartitionSupervisor, а ключ используется для маршрутизации.
Простой пример
Начнём с примера, который сам по себе бесполезен, но демонстрирует, как запускаются партиции и как сообщения маршрутизируются к ним.
Вот игрушечный GenServer, который просто собирает полученные сообщения. Он выводит их для наглядности.
defmodule Collector do
use GenServer
def start_link(args) do
GenServer.start_link(__MODULE__, args)
end
def init(args) do
IO.inspect [__MODULE__, " got args ", args, " in ", self()]
{:ok, _initial_state = []}
end
def collect(server, msg) do
GenServer.call(server, {:collect, msg})
end
def handle_call({:collect, msg}, _from, state) do
new_state = [msg | state]
IO.inspect ["current messages:", new_state, " in process", self()]
{:reply, :ok, new_state}
end
end
Для запуска нескольких таких процессов можно запустить их под PartitionSupervisor, разместив это в дереве контроля:
{PartitionSupervisor,
child_spec: Collector.child_spec([some: :arg]),
name: MyApp.PartitionSupervisor
}
Мы можем отправлять сообщения к ним, используя «кортеж в качестве адресата»:
# The key is used to route our message to a particular instance.
key = 1
Collector.collect({:via, PartitionSupervisor, {MyApp.PartitionSupervisor, key}}, :hi)
# ["current messages:", [:hi], " in process", #PID<0.602.0>]
:ok
Collector.collect({:via, PartitionSupervisor, {MyApp.PartitionSupervisor, key}}, :ho)
# ["current messages:", [:ho, :hi], " in process", #PID<0.602.0>]
:ok
# With a different key, the message will be routed to a different instance.
key = 2
Collector.collect({:via, PartitionSupervisor, {MyApp.PartitionSupervisor, key}}, :a)
# ["current messages:", [:a], " in process", #PID<0.603.0>]
:ok
Collector.collect({:via, PartitionSupervisor, {MyApp.PartitionSupervisor, key}}, :b)
# ["current messages:", [:b, :a], " in process", #PID<0.603.0>]
:ok
Теперь перейдём к полезному примеру.
DynamicSupervisor Пример
DynamicSupervisor — это единственный процесс, ответственный за запуск других процессов. В некоторых приложениях DynamicSupervisor может стать узким местом. Для решения этой проблемы вы можете запустить несколько экземпляров DynamicSupervisor через PartitionSupervisor и затем выбрать «случайный» экземпляр для запуска дочернего процесса.
Вместо запуска одного DynamicSupervisor:
children = [
{DynamicSupervisor, name: MyApp.DynamicSupervisor}
]
Supervisor.start_link(children, strategy: :one_for_one)
и запуска дочерних процессов на этом динамическом надзирателе напрямую:
DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Agent, fn -> %{} end})
Можно запустить динамических надзирателей под PartitionSupervisor:
children = [
{PartitionSupervisor,
child_spec: DynamicSupervisor,
name: MyApp.DynamicSupervisors}
]
Supervisor.start_link(children, strategy: :one_for_one)
и затем:
DynamicSupervisor.start_child(
{:via, PartitionSupervisor, {MyApp.DynamicSupervisors, self()}},
{Agent, fn -> %{} end}
)
В приведенном коде мы запускаем надзирателя партиций, который по умолчанию запустит динамического надзирателя для каждого ядра вашего компьютера. Затем вместо вызова DynamicSupervisor по имени, вы вызываете его через надзирателя партиций, используя формат {:via, PartitionSupervisor, {name, key}}. Мы выбрали self() в качестве ключа маршрутизации, что означает, что каждому процессу будет назначен один из существующих динамических надзирателей. См. start_link/1, чтобы увидеть все поддерживаемые варианты PartitionSupervisor.
Примечания по реализации
PartitionSupervisor использует таблицу ETS или Registry для управления всеми партициями. Под капотом PartitionSupervisor генерирует спецификацию дочернего процесса для каждой партиции и затем действует как обычный надзиратель. Идентификатор каждой спецификации дочернего процесса — номер партиции.
Для маршрутизации используются две стратегии. Если key — целое число, оно маршрутизируется с использованием rem(abs(key), partitions), где partitions — число партиций. В противном случае используется :erlang.phash2(key, partitions). Конкретная маршрутизация может измениться в будущем, поэтому на ней не следует полагаться. Если вам нужно получить определённый PID для определённого ключа, можно использовать GenServer.whereis({:via, PartitionSupervisor, {name, key}}).
Типы
Функции
- count_children(supervisor)
Возвращает карту, содержащую значения подсчёта для надзирателя.
- partitions(name)
Возвращает количество партиций для надзирателя партиций.
- start_link(opts)
Запускает надзирателя партиций с заданными опциями.
- stop(supervisor, reason \\ :normal, timeout \\ :infinity)
Синхронно останавливает данный надзиратель партиций с заданным
reason.- which_children(name)
Возвращает список с информацией обо всех дочерних процессах.
name()Source
@type name() :: atom() | {:via, module(), term()} Имя PartitionSupervisor.
count_children(supervisor)Source
@spec count_children(name()) :: %{
specs: non_neg_integer(),
active: non_neg_integer(),
supervisors: non_neg_integer(),
workers: non_neg_integer()
} Возвращает карту, содержащую значения подсчета для супервайзера.
Карта содержит следующие ключи:
:specs- количество разделов (дочерних процессов):active- количество всех активно работающих дочерних процессов, управляемых этим супервайзером:supervisors- количество всех супервайзеров, независимо от того, жив ли дочерний процесс:workers- количество всех рабочих процессов, независимо от того, жив ли дочерний процесс
partitions(name)Source
@spec partitions(name()) :: pos_integer()
Возвращает количество разделов для супервайзера разделов.
start_link(opts)Source
@spec start_link(keyword()) :: Supervisor.on_start()
Запускает супервайзера разделов с заданными параметрами.
Эта функция обычно не вызывается напрямую, вместо этого она вызывается при использовании PartitionSupervisor в качестве дочернего элемента другого супервайзера:
children = [
{PartitionSupervisor, child_spec: SomeChild, name: MyPartitionSupervisor}
]
Если супервайзер успешно запущен, эта функция возвращает {:ok, pid}, где pid — PID супервайзера. Если указанное имя для супервайзера разделов уже назначено процессу, функция возвращает {:error, {:already_started, pid}}, где pid — PID этого процесса.
Обратите внимание, что супервайзер, запущенный с помощью этой функции, связан с родительским процессом и завершается не только при сбоях, но и если родительский процесс завершается с :normal причиной.
Параметры
:name— атом или кортеж, представляющий имя супервайзера разделов (см.name/0).:child_spec— описание дочернего элемента, используемое при запуске разделов.:partitions— положительное целое число, обозначающее количество разделов. По умолчаниюSystem.schedulers_online()(обычно количество ядер).:strategy— параметр стратегии перезапуска, по умолчанию:one_for_one. Подробнее о стратегиях можно узнать в документации модуляSupervisor.:max_restarts— максимальное количество перезапусков, разрешенных в заданный период времени. По умолчанию3.:max_seconds— период времени, в течение которого действует:max_restarts. По умолчанию5.:with_arguments— анонимная функция с двумя аргументами, которая позволяет передать разбиение функции запуска дочернего процесса. См. раздел:with_argumentsниже.
:with_arguments
Иногда нужно, чтобы каждый раздел знал свой присвоенный номер раздела. Это можно сделать с помощью параметра :with_arguments. Эта функция получает значение параметра :child_spec и целое число для номера раздела. Она должна вернуть новый список аргументов, который будет использован для запуска процесса раздела.
Например, большинство процессов запускаются с помощью вызова start_link(opts), где opts — список ключевых слов. Вы можете вставить раздел в параметры, предоставляемые дочернему процессу:
with_arguments: fn [opts], partition -> [Keyword.put(opts, :partition, partition)] end
stop(supervisor, reason \\ :normal, timeout \\ :infinity)Source
@spec stop(name(), reason :: term(), timeout()) :: :ok
Синхронно останавливает данный супервайзер разделов с заданной reason причиной.
Возвращает :ok если супервайзер завершается с заданной причиной. Если он завершается с другой причиной, вызов завершается.
Эта функция поддерживает семантику OTP относительно отчетов об ошибках. Если причина отличается от :normal, :shutdown или {:shutdown, _}, регистрируется отчет об ошибке.
which_children(name)Source
@spec which_children(name()) :: [
{:undefined, pid() | :restarting, :worker | :supervisor,
[module()] | :dynamic}
] Возвращает список с информацией обо всех дочерних элементах.
Эта функция возвращает список кортежей, содержащих:
id- номер разделаchild- PID соответствующего дочернего процесса или атом:restartingесли процесс собирается перезапуститьсяtype-:workerили:supervisorкак определено в спецификации дочернего элементаmodules- как определено в спецификации дочернего элемента
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.15.4/PartitionSupervisor.html