Spec-Zone.ru › Elixir 1.17

Источник 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)

Возвращает количество партиций для надзорщика партиций.

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()

Возвращает количество разделов для супервайзера разделов.

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

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

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

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

Spec-Zone.ru

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