Исходный код PartitionSupervisor
Наблюдатель, запускающий несколько разделов одного и того же дочернего процесса.
В больших системах некоторые процессы могут стать узкими местами. Если состояние таких процессов можно тривиально разделить, при этом между ними нет зависимости, то они могут использовать PartitionSupervisor для создания нескольких изолированных и независимых разделов.
После запуска PartitionSupervisor вы можете отправлять сообщения его дочерним процессам, используя {:via, PartitionSupervisor, {name, key}}, где name — имя PartitionSupervisor, а ключ используется для маршрутизации.
Этот модуль был представлен в Elixir v1.14.0.
Простой пример
Начнём с примера, который сам по себе не очень полезен, но демонстрирует, как запускаются разделы и как маршрутизируются сообщения.
Вот пример 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)
Возвращает количество разделов для наблюдателя разделов.
- resize!(name, partitions)
Изменяет количество разделов в PartitionSupervisor.
- start_link(opts)
Запускает наблюдателя разделов с заданными параметрами.
- stop(supervisor, reason \\ :normal, timeout \\ :infinity)
Синхронно останавливает заданного наблюдателя разделов с указанным
reason.- which_children(name)
Возвращает список с информацией обо всех дочерних процессах.
Типы
name()Исходный код
@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()
Возвращает количество партиций для супервайзора партиций.
resize!(name, partitions)Source
@spec resize!(name(), non_neg_integer()) :: non_neg_integer()
Изменяет количество партиций в PartitionSupervisor.
Это делается путем запуска или остановки заданного количества партиций в супервайзоре. Все спецификации дочерних процессов сохраняются в PartitionSupervisor самом.
Конечное количество партиций не может быть меньше нуля и не может быть больше количества партиций, с которым супервайзор был запущен.
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()) :: [
{integer(), pid() | :restarting, :worker | :supervisor, [module()] | :dynamic}
] Возвращает список с информацией обо всех дочерних процессах.
Эта функция возвращает список кортежей, содержащих:
id- номер партицииchild- PID соответствующего дочернего процесса или атом:restartingесли процесс собирается перезапуститьсяtype-:workerили:supervisorкак определено в спецификации дочернего процессаmodules- как определено в спецификации дочернего процесса
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.18.1/PartitionSupervisor.html