Spec-Zone.ru › Elixir 1.6

Регистр

Локальное, децентрализованное и масштабируемое хранилище данных ключ-значение для процессов.

Он позволяет разработчикам искать один или несколько процессов по заданному ключу. Если в реестре есть :unique ключи, ключ указывает на 0 или 1 процесс. Если реестр разрешает :duplicate ключи, один ключ может указывать на любое количество процессов. В обоих случаях разные ключи могут идентифицировать один и тот же процесс.

Каждая запись в реестре связана с процессом, который зарегистрировал ключ. Если процесс аварийно завершается, ключи, связанные с этим процессом, автоматически удаляются. Все сравнения ключей в реестре выполняются с использованием операции совпадения (===).

Регистр можно использовать для различных целей, таких как поиск по имени (используя :via вариант), хранение свойств, пользовательские правила диспетчеризации или реализацию системы публикация-подписка. Ниже мы рассмотрим некоторые из этих вариантов использования.

Регистр также может быть прозрачно разбит на части, что обеспечивает более масштабируемое поведение для работы реестров в высококонкурентных средах с тысячами или миллионами записей.

Использование в :via

После запуска реестра с заданным именем с помощью Registry.start_link/1, его можно использовать для регистрации и доступа к именованным процессам, используя кортеж {:via, Registry, {registry, key}}:

{:ok, _} = Registry.start_link(keys: :unique, name: Registry.ViaTest)
name = {:via, Registry, {Registry.ViaTest, "agent"}}
{:ok, _} = Agent.start_link(fn -> 0 end, name: name)
Agent.get(name, & &1)
#=> 0
Agent.update(name, & &1 + 1)
Agent.get(name, & &1)
#=> 1

Как правило, реестр запускается как часть дерева управления:

{Registry, keys: :unique, name: Registry.ViaTest}

Только реестры с уникальными ключами могут использоваться в :via. Если имя уже занято, функция start_link (Agent.start_link/2 в примере выше) вернёт {:error, {:already_started, current_pid}}.

Использование в качестве диспетчера

Registry имеет механизм диспетчеризации, позволяющий разработчикам реализовывать пользовательскую логику диспетчеризации, запускаемую вызывающим процессом. Например, предположим, что у нас есть дублируемый реестр, запущенный следующим образом:

{:ok, _} = Registry.start_link(keys: :duplicate, name: Registry.DispatcherTest)

Вызвав register/3, разные процессы могут регистрироваться под заданным ключом и ассоциировать любое значение с этим ключом. В этом случае давайте зарегистрируем текущий процесс под ключом "hello" и присоединим к нему кортеж {IO, :inspect}:

{:ok, _} = Registry.register(Registry.DispatcherTest, "hello", {IO, :inspect})

Теперь сущность, заинтересованная в диспетчеризации событий для данного ключа, может вызвать dispatch/3, передав ключ и обратный вызов. Этот обратный вызов будет вызван со списком всех значений, зарегистрированных под запрошенным ключом, вместе с pid процесса, который зарегистрировал каждое значение, в виде кортежей {pid, value}. В нашем примере, value будет кортежем {module, function} в коде выше:

Registry.dispatch(Registry.DispatcherTest, "hello", fn entries ->
  for {pid, {module, function}} <- entries, do: apply(module, function, [pid])
end)
# Prints #PID<...> where the pid is for the process that called register/3 above
#=> :ok

Диспетчеризация происходит в процессе, который вызывает dispatch/3, либо последовательно, либо параллельно в случае нескольких разделов (через запущенные задачи). Зарегистрированные процессы не участвуют в диспетчеризации, если только их участие не задано явно (например, путем отправки им сообщения в обратном вызове).

Кроме того, если при диспетчеризации произойдет ошибка из-за неправильной регистрации, диспетчеризация всегда завершится ошибкой, и зарегистрированный процесс не будет уведомлен. Поэтому давайте позаботимся о том, чтобы мы по крайней мере оборачивали и сообщали об этих ошибках:

require Logger
Registry.dispatch(Registry.DispatcherTest, "hello", fn entries ->
  for {pid, {module, function}} <- entries do
    try do
      apply(module, function, [pid])
    catch
      kind, reason ->
        formatted = Exception.format(kind, reason, System.stacktrace)
        Logger.error "Registry.dispatch/3 failed with #{formatted}"
    end
  end
end)
# Prints #PID<...>
#=> :ok

Вы также можете заменить всю систему apply явным отправлением сообщений. Это пример, который мы увидим дальше.

Использование в качестве PubSub

Реестры также могут использоваться для реализации локальной, нераспределенной и масштабируемой PubSub, опираясь на функцию dispatch/3, аналогично предыдущему разделу: в этом случае, однако, мы будем отправлять сообщения каждому связанному процессу, а не вызывать заданную функцию модуля.

В этом примере мы также установим количество разделов в число активных планировщиков, что сделает реестр более производительным в высококонкурентных средах:

{:ok, _} = Registry.start_link(keys: :duplicate, name: Registry.PubSubTest,
                               partitions: System.schedulers_online)
{:ok, _} = Registry.register(Registry.PubSubTest, "hello", [])
Registry.dispatch(Registry.PubSubTest, "hello", fn entries ->
  for {pid, _} <- entries, do: send(pid, {:broadcast, "world"})
end)
#=> :ok

В приведенном выше примере сообщение {:broadcast, "world"} было разослано всем процессам, зарегистрированным под «темой» (или «ключом», как мы его называли до сих пор) "hello".

Третий аргумент, передаваемый в register/3, представляет собой значение, связанное с текущим процессом. В предыдущем разделе мы использовали его при диспетчеризации, но в этом конкретном примере нас это не интересует, поэтому мы установили его в пустой список. При необходимости вы можете сохранить более значимое значение.

Регистрации

Поиск, диспетчеризация и регистрация эффективны и мгновенны за счет задержки отписки. Например, если процесс аварийно завершается, его ключи автоматически удаляются из реестра, но изменение может не распространиться немедленно. Это означает, что некоторые операции могут возвращать процессы, которые уже завершены. В документации функции будет явно указано, когда это может произойти.

Однако имейте в виду, что такие случаи обычно не являются проблемой. В конце концов, процесс, на который ссылается pid, может аварийно завершиться в любое время, в том числе между получением значения из реестра и отправкой ему сообщения. Многие части стандартной библиотеки разработаны для работы с этим, например, Process.monitor/1, которая будет немедленно доставлять сообщение :DOWN , если отслеживаемый процесс уже завершен, и Kernel.send/2, которая действует как no-op для завершенных процессов.

ETS

Обратите внимание, что реестр использует одну таблицу ETS плюс две таблицы ETS на раздел.

Резюме

Типы

key()

Тип ключей, разрешенных при регистрации

keys()

Тип реестра

meta_key()

Тип метаданных ключей реестра

meta_value()

Тип метаданных значений реестра

registry()

Идентификатор реестра

value()

Тип значений, разрешенных при регистрации

Функции

child_spec(opts)

Возвращает спецификацию для запуска реестра под супервайзером

dispatch(registry, key, mfa_or_fun, opts \\ [])

Вызывает обратный вызов со всеми записями под key в каждом разделе для данного registry.

keys(registry, pid)

Возвращает известные ключи для данного pid в registry в произвольном порядке.

lookup(registry, key)

Находит пару {pid, value} для данного key в registry в произвольном порядке.

match(registry, key, pattern, guards \\ [])

Возвращает пары {pid, value} под заданным key в registry , которые соответствуют pattern.

meta(registry, key)

Считывает метаданные реестра, заданные в start_link/3.

put_meta(registry, key, value)

Хранит метаданные реестра.

register(registry, key, value)

Регистрирует текущий процесс под заданным key в registry.

start_link(options)

Запускает реестр как процесс супервайзера.

start_link(keys, name, options \\ [])

Запускает реестр как процесс супервайзера.

unregister(registry, key)

Удаляет все записи для данного key , связанные с текущим процессом в registry.

unregister_match(registry, key, pattern, guards \\ [])

Удаляет записи для данного ключа, соответствующие шаблону.

update_value(registry, key, callback)

Обновляет значение для key для текущего процесса в уникальном registry.

Типы

key()

key() :: term()

Тип ключей, разрешенных при регистрации

keys()

keys() :: :unique | :duplicate

Тип реестра

meta_key()

meta_key() :: atom() | tuple()

Тип метаданных ключей реестра

meta_value()

meta_value() :: term()

Тип метаданных значений реестра

registry()

registry() :: atom()

Идентификатор реестра

value()

value() :: term()

Тип значений, разрешенных при регистрации

Функции

child_spec(opts)

Возвращает спецификацию для запуска реестра под супервайзером.

См. Supervisor.

dispatch(registry, key, mfa_or_fun, opts \\ [])

dispatch(
  registry(),
  key(),
  (entries :: [{pid(), value()}] -> term()),
  keyword()
) :: :ok

Вызывает обратный вызов со всеми записями под key в каждом разделе для данного registry.

Список entries — это список непустых кортежей из двух элементов, где первый элемент — pid, а второй — значение, связанное с pid. Если для данного ключа нет записей, обратный вызов никогда не вызывается.

Если реестр разделен, обратный вызов вызывается несколько раз для каждого раздела. Если реестр разделен и parallel: true задан в качестве параметра, диспетчеризация происходит параллельно. В обоих случаях обратный вызов вызывается только если есть записи для этого раздела.

См. документацию модуля для примеров использования функции dispatch/3 для создания пользовательской диспетчеризации или системы pubsub.

keys(registry, pid)

keys(registry(), pid()) :: [key()]

Возвращает известные ключи для данного pid в registry в произвольном порядке.

Если реестр уникальный, ключи уникальны. В противном случае они могут содержать дубликаты, если процесс был зарегистрирован под одним и тем же ключом несколько раз. Список будет пустым, если процесс мертв или у него нет ключей в этом реестре.

Примеры

Регистрация под уникальным реестром не допускает нескольких записей:

iex> Registry.start_link(:unique, Registry.UniqueKeysTest)
iex> Registry.keys(Registry.UniqueKeysTest, self())
[]
iex> {:ok, _} = Registry.register(Registry.UniqueKeysTest, "hello", :world)
iex> Registry.register(Registry.UniqueKeysTest, "hello", :later) # registry is :unique
{:error, {:already_registered, self()}}
iex> Registry.keys(Registry.UniqueKeysTest, self())
["hello"]

Однако это возможно для дублирующихся реестров:

iex> Registry.start_link(:duplicate, Registry.DuplicateKeysTest)
iex> Registry.keys(Registry.DuplicateKeysTest, self())
[]
iex> {:ok, _} = Registry.register(Registry.DuplicateKeysTest, "hello", :world)
iex> {:ok, _} = Registry.register(Registry.DuplicateKeysTest, "hello", :world)
iex> Registry.keys(Registry.DuplicateKeysTest, self())
["hello", "hello"]

lookup(registry, key)

lookup(registry(), key()) :: [{pid(), value()}]

Находит пару {pid, value} для данного key в registry в произвольном порядке.

Пустой список, если совпадений нет.

Для уникальных реестров необходим поиск по одному разделу. Для дублирующихся реестров необходимо выполнить поиск по всем разделам.

Примеры

В примере ниже мы регистрируем текущий процесс и ищем его как из самого себя, так и из других процессов:

iex> Registry.start_link(:unique, Registry.UniqueLookupTest)
iex> Registry.lookup(Registry.UniqueLookupTest, "hello")
[]
iex> {:ok, _} = Registry.register(Registry.UniqueLookupTest, "hello", :world)
iex> Registry.lookup(Registry.UniqueLookupTest, "hello")
[{self(), :world}]
iex> Task.async(fn -> Registry.lookup(Registry.UniqueLookupTest, "hello") end) |> Task.await
[{self(), :world}]

То же самое относится к дублирующим реестрам:

iex> Registry.start_link(:duplicate, Registry.DuplicateLookupTest)
iex> Registry.lookup(Registry.DuplicateLookupTest, "hello")
[]
iex> {:ok, _} = Registry.register(Registry.DuplicateLookupTest, "hello", :world)
iex> Registry.lookup(Registry.DuplicateLookupTest, "hello")
[{self(), :world}]
iex> {:ok, _} = Registry.register(Registry.DuplicateLookupTest, "hello", :another)
iex> Enum.sort(Registry.lookup(Registry.DuplicateLookupTest, "hello"))
[{self(), :another}, {self(), :world}]

match(registry, key, pattern, guards \\ [])

match(registry(), key(), match_pattern :: term(), guards :: list()) :: [
  {pid(), term()}
]

Возвращает {pid, value} пары под заданным key в registry которые соответствуют pattern.

Шаблон должен быть атомом или кортежем, который будет соответствовать структуре значения, хранящегося в реестре. Атом :_ может использоваться для игнорирования заданного значения или элемента кортежа, а :”$1” может использоваться для временного назначения части шаблона переменной для последующего сравнения.

Можно передать список условий защиты для более точного соответствия. Каждое условие защиты — это кортеж, который описывает проверку, которую должна пройти назначенная часть шаблона. Например, условие защиты :”$1” > 1 будет выражено как кортеж {:>, :”$1”, 1}. Обратите внимание, что условия защиты будут работать только для назначенных переменных, таких как :”$1”, :”$2” и т. д. Избегайте использования специальных переменных сопоставления :”$_” и :”$$”, так как они могут работать не так, как ожидается.

Если совпадений нет, будет возвращен пустой список.

Для уникальных реестров необходим поиск по одному разделу. Для дублирующихся реестров необходимо выполнить поиск по всем разделам.

Примеры

В примере ниже мы регистрируем текущий процесс под одним и тем же ключом в дублирующем реестре, но с разными значениями:

iex> Registry.start_link(:duplicate, Registry.MatchTest)
iex> {:ok, _} = Registry.register(Registry.MatchTest, "hello", {1, :atom, 1})
iex> {:ok, _} = Registry.register(Registry.MatchTest, "hello", {2, :atom, 2})
iex> Registry.match(Registry.MatchTest, "hello", {1, :_, :_})
[{self(), {1, :atom, 1}}]
iex> Registry.match(Registry.MatchTest, "hello", {2, :_, :_})
[{self(), {2, :atom, 2}}]
iex> Registry.match(Registry.MatchTest, "hello", {:_, :atom, :_}) |> Enum.sort()
[{self(), {1, :atom, 1}}, {self(), {2, :atom, 2}}]
iex> Registry.match(Registry.MatchTest, "hello", {:"$1", :_, :"$1"}) |> Enum.sort()
[{self(), {1, :atom, 1}}, {self(), {2, :atom, 2}}]
iex> Registry.match(Registry.MatchTest, "hello", {:_, :_, :"$1"}, [{:>, :"$1", 1}])
[{self(), {2, :atom, 2}}]
iex> Registry.match(Registry.MatchTest, "hello", {:_, :"$1", :_}, [{:is_atom, :"$1"}]) |> Enum.sort()
[{self(), {1, :atom, 1}}, {self(), {2, :atom, 2}}]

meta(registry, key)

meta(registry(), meta_key()) :: {:ok, meta_value()} | :error

Считывает метаданные реестра, заданные в start_link/3.

В качестве ключей разрешены атомы и кортежи.

Примеры

iex> Registry.start_link(:unique, Registry.MetaTest, meta: [custom_key: "custom_value"])
iex> Registry.meta(Registry.MetaTest, :custom_key)
{:ok, "custom_value"}
iex> Registry.meta(Registry.MetaTest, :unknown_key)
:error

put_meta(registry, key, value)

put_meta(registry(), meta_key(), meta_value()) :: :ok

Сохраняет метаданные реестра.

В качестве ключей разрешены атомы и кортежи.

Примеры

iex> Registry.start_link(:unique, Registry.PutMetaTest)
iex> Registry.put_meta(Registry.PutMetaTest, :custom_key, "custom_value")
:ok
iex> Registry.meta(Registry.PutMetaTest, :custom_key)
{:ok, "custom_value"}
iex> Registry.put_meta(Registry.PutMetaTest, {:tuple, :key}, "tuple_value")
:ok
iex> Registry.meta(Registry.PutMetaTest, {:tuple, :key})
{:ok, "tuple_value"}

register(registry, key, value)

register(registry(), key(), value()) ::
  {:ok, pid()} | {:error, {:already_registered, pid()}}

Регистрирует текущий процесс под заданным key в registry.

Также должно быть указано значение, которое будет ассоциироваться с этой регистрацией. Это значение будет извлечено при диспетчеризации или поиске по ключу.

Эта функция возвращает {:ok, owner} или {:error, reason}. owner — это pid в разделе реестра, отвечающем за pid. Владелец автоматически связан с вызывающей стороной.

Если реестр имеет уникальные ключи, он вернет {:ok, owner}, если ключ уже не связан с pid, в противном случае он вернет {:error, {:already_registered, pid}}.

Если реестр имеет дублирующиеся ключи, несколько регистраций текущего процесса под одним и тем же ключом разрешены.

Примеры

Регистрация под уникальным реестром не допускает нескольких записей:

iex> Registry.start_link(:unique, Registry.UniqueRegisterTest)
iex> {:ok, _} = Registry.register(Registry.UniqueRegisterTest, "hello", :world)
iex> Registry.register(Registry.UniqueRegisterTest, "hello", :later)
{:error, {:already_registered, self()}}
iex> Registry.keys(Registry.UniqueRegisterTest, self())
["hello"]

Однако это возможно для дублирующихся реестров:

iex> Registry.start_link(:duplicate, Registry.DuplicateRegisterTest)
iex> {:ok, _} = Registry.register(Registry.DuplicateRegisterTest, "hello", :world)
iex> {:ok, _} = Registry.register(Registry.DuplicateRegisterTest, "hello", :world)
iex> Registry.keys(Registry.DuplicateRegisterTest, self())
["hello", "hello"]

start_link(options)

start_link(
  keys: keys(),
  name: registry(),
  partitions: pos_integer(),
  listeners: [atom()],
  meta: meta
) :: {:ok, pid()} | {:error, term()}
when meta: [{meta_key(), meta_value()}]

Запускает реестр как процесс-надзирателя.

Ручной запуск:

Registry.start_link(keys: :unique, name: MyApp.Registry)

В вашей дереве надзирателей вы напишите:

Supervisor.start_link([
  {Registry, keys: :unique, name: MyApp.Registry}
])

Для интенсивных задач, реестр также может быть разделен (указав параметр :partitions). Если разделение необходимо, то хорошим значением по умолчанию является установка количества разделов равным количеству доступных планировщиков:

Registry.start_link(keys: :unique, name: MyApp.Registry,
                    partitions: System.schedulers_online())

или:

Supervisor.start_link([
  {Registry, keys: :unique, name: MyApp.Registry,
             partitions: System.schedulers_online()}
])

Параметры

Реестр требует следующих ключей:

  • :keys - выбрать, являются ли ключи :unique или :duplicate
  • :name - имя реестра и его таблиц

Следующие ключи необязательны:

  • :partitions - количество разделов в реестре. По умолчанию 1.
  • :listeners - список именованных процессов, которые уведомляются о событиях :register и :unregister. Зарегистрированный процесс должен отслеживаться слушателем, если слушатель хочет быть уведомленным о сбоях зарегистрированного процесса.
  • :meta - список ключевых слов метаданных, которые должны быть добавлены к реестру.

start_link(keys, name, options \\ [])

start_link(keys(), registry(), keyword()) :: {:ok, pid()} | {:error, term()}

Запускает реестр как процесс-надзирателя.

Аналогично start_link/1, за исключением того, что необходимые параметры keys и name передаются в качестве аргументов.

unregister(registry, key)

unregister(registry(), key()) :: :ok

Удаляет все записи для данного key, связанные с текущим процессом в registry.

Всегда возвращает :ok и автоматически разрывает связь текущего процесса с владельцем, если больше нет ключей, связанных с текущим процессом. Также см. register/3, чтобы узнать больше о «владельце».

Примеры

Для уникальных реестров:

iex> Registry.start_link(:unique, Registry.UniqueUnregisterTest)
iex> Registry.register(Registry.UniqueUnregisterTest, "hello", :world)
iex> Registry.keys(Registry.UniqueUnregisterTest, self())
["hello"]
iex> Registry.unregister(Registry.UniqueUnregisterTest, "hello")
:ok
iex> Registry.keys(Registry.UniqueUnregisterTest, self())
[]

Для дублирующихся реестров:

iex> Registry.start_link(:duplicate, Registry.DuplicateUnregisterTest)
iex> Registry.register(Registry.DuplicateUnregisterTest, "hello", :world)
iex> Registry.register(Registry.DuplicateUnregisterTest, "hello", :world)
iex> Registry.keys(Registry.DuplicateUnregisterTest, self())
["hello", "hello"]
iex> Registry.unregister(Registry.DuplicateUnregisterTest, "hello")
:ok
iex> Registry.keys(Registry.DuplicateUnregisterTest, self())
[]

unregister_match(registry, key, pattern, guards \\ [])

Удаляет записи для заданного ключа, соответствующего шаблону.

Примеры

Для уникальных реестров его можно использовать для условного удаления ключа на основе того, соответствует ли он определенному значению.

iex> Registry.start_link(:unique, Registry.UniqueUnregisterMatchTest)
iex> Registry.register(Registry.UniqueUnregisterMatchTest, "hello", :world)
iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
["hello"]
iex> Registry.unregister_match(Registry.UniqueUnregisterMatchTest, "hello", :foo)
:ok
iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
["hello"]
iex> Registry.unregister_match(Registry.UniqueUnregisterMatchTest, "hello", :world)
:ok
iex> Registry.keys(Registry.UniqueUnregisterMatchTest, self())
[]

Для дублирующихся реестров:

iex> Registry.start_link(:duplicate, Registry.DuplicateUnregisterMatchTest)
iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_a)
iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_b)
iex> Registry.register(Registry.DuplicateUnregisterMatchTest, "hello", :world_c)
iex> Registry.keys(Registry.DuplicateUnregisterMatchTest, self())
["hello", "hello", "hello"]
iex> Registry.unregister_match(Registry.DuplicateUnregisterMatchTest, "hello", :world_a)
:ok
iex> Registry.keys(Registry.DuplicateUnregisterMatchTest, self())
["hello", "hello"]
iex> Registry.lookup(Registry.DuplicateUnregisterMatchTest, "hello")
[{self(), :world_b}, {self(), :world_c}]

update_value(registry, key, callback)

update_value(registry(), key(), (value() -> value())) ::
  {new_value :: term(), old_value :: term()} | :error

Обновляет значение для key для текущего процесса в уникальном registry.

Возвращает кортеж {new_value, old_value} или :error если такому ключу не назначен текущий процесс.

Если задан неуникальный реестр, генерируется ошибка.

Примеры

iex> Registry.start_link(:unique, Registry.UpdateTest)
iex> {:ok, _} = Registry.register(Registry.UpdateTest, "hello", 1)
iex> Registry.lookup(Registry.UpdateTest, "hello")
[{self(), 1}]
iex> Registry.update_value(Registry.UpdateTest, "hello", & &1 + 1)
{2, 1}
iex> Registry.lookup(Registry.UpdateTest, "hello")
[{self(), 2}]

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.6.6/Registry.html

Spec-Zone.ru

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