Spec-Zone.ru › Elixir 1.18

Исходный код 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}}).

Сводка

Типы

name()

Имя PartitionSupervisor.

Функции

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 - как определено в спецификации дочернего процесса

Скачать версию ePub

Создано с помощью ExDoc (v0.36.1) для программного языка Elixir

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.18.1/PartitionSupervisor.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API