Spec-Zone.ru › Elixir 1.16

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

Чтобы запустить несколько таких GenServer'ов, мы можем запустить их под 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.32.2) для языка программирования Elixir

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

Spec-Zone.ru

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