Как использовать Registry для хранения состояния?

Registry в Elixir — это встроенный модуль, предоставляющий функциональность именованной регистрации и поиска процессов. Он используется как альтернатива глобальным именам, чтобы управлять процессами через уникальные ключи, особенно в распределённых и масштабируемых системах. В отличие от ETS или Agent, Registry не хранит произвольные данные — он хранит соответствие между именами (ключами) и PID-ами процессов.

Основные возможности Registry

  • Хранение связки: имя (ключ) → PID процесса.

  • Поддержка уникальной и множественной регистрации.

  • Возможность трассировки и подписки на события.

  • Используется для поиска и адресации процессов по ключу.

Инициализация Registry

В первую очередь, Registry нужно запустить и указать, как он будет работать: в режиме :unique или :duplicate.

children = \[
{Registry, keys: :unique, name: MyApp.Registry}
\]
Supervisor.start_link(children, strategy: :one_for_one)
  • keys: :unique — каждый ключ соответствует только одному PID.

  • keys: :duplicate — несколько процессов могут быть зарегистрированы под одним ключом.

  • name: MyApp.Registry — имя, через которое мы будем к нему обращаться.

Регистрация процесса

Процесс может зарегистрироваться в Registry через Registry.register/3.

{:ok, \_} = Registry.register(MyApp.Registry, "user_123", %{status: :online})
  • "user_123" — ключ (может быть любой термин: строка, tuple, atom).

  • %{status: :online} — значение (полезная нагрузка, metadata).

  • Функция вернёт {:ok, _} если регистрация успешна, или {:error, {:already_registered, pid}}.

Поиск зарегистрированных процессов

Для получения PID, связанного с определённым ключом:

\[{pid, \_meta}\] = Registry.lookup(MyApp.Registry, "user_123")

Если процесс не найден — результат будет [].

Использование start_link с именем через Registry

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

GenServer.start_link(MyModule, arg, name: {:via, Registry, {MyApp.Registry, "user_123"}})

Теперь вы можете отправлять сообщения этому GenServer по ключу:

GenServer.call({:via, Registry, {MyApp.Registry, "user_123"}}, :ping)

Работа в режиме :duplicate

Если Registry создан с keys: :duplicate, то несколько процессов могут быть зарегистрированы под одним и тем же ключом. Это удобно для систем подписки:

Registry.register(MyApp.Registry, :chat_room, %{user: "Vasya"})

Чтобы отправить сообщение всем процессам, зарегистрированным под :chat_room:

for {pid, meta} <- Registry.lookup(MyApp.Registry, :chat_room) do
send(pid, {:message, meta.user})
end

Удаление и автоматическое освобождение ключей

Registry автоматически удаляет записи, если зарегистрированный процесс умирает. Это встроено и не требует ручного удаления.

Подписка на события (via Registry.dispatch/3 и Registry.select/2)

Если используется :duplicate, можно создавать сложные подписки:

Registry.dispatch(MyApp.Registry, :chat_room, fn entries ->
for {pid, meta} <- entries do
send(pid, {:new_message, "Привет!"})
end
end)

Пример полного использования

defmodule Worker do
use GenServer
def start_link(user_id) do
GenServer.start_link(\__MODULE_\_, user_id,
name: {:via, Registry, {MyApp.Registry, user_id}})
end
def init(user_id) do
{:ok, %{user: user_id}}
end
def handle_call(:get_user, \_from, state) do
{:reply, state.user, state}
end
end
\# Запуск Supervisor с Registry
Supervisor.start_link(
\[
{Registry, keys: :unique, name: MyApp.Registry}
\],
strategy: :one_for_one
)
\# Создание процесса и регистрация
Worker.start_link("user_1")
\# Вызов по ключу
GenServer.call({:via, Registry, {MyApp.Registry, "user_1"}}, :get_user)

Особенности и ограничения

  • Registry не предназначен для хранения произвольных данных, как ETS.

  • Он заточен под процессы, и теряет данные при завершении связанных PID.

  • Нельзя получить полный список всех ключей напрямую — нужно использовать :ets.tab2list/1, если критично.

Registry — мощный инструмент адресации и подписки на события между процессами, особенно полезен при построении динамически масштабируемых систем, таких как чаты, игры, распределённые очереди и др.