Spec-Zone.ru › Elixir 1.5

Реестр

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

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

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

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

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

Использование в :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, 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, которая действует как «ничего не делать» для завершенных процессов.

ETS

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

Сводка

Типы

key()

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

keys()

Тип реестра

meta_key()

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

meta_value()

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

registry()

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

value()

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

Функции

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

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

Функции

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 :: atom() | tuple(), 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 — это идентификатор процесса в разделе реестра, отвечающем за этот идентификатор процесса. Владелец автоматически связан с вызывающим процессом.

Если в реестре уникальные ключи, то будет возвращено {:ok, owner}, если ключ не ассоциирован с идентификатором процесса, иначе будет возвращено {: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_key(), meta_value()}]) ::
  {:ok, pid()} |
  {:error, term()}

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

Вручную его можно запустить как:

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.5.3/Registry.html

Spec-Zone.ru

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