Как использовать 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 — мощный инструмент адресации и подписки на события между процессами, особенно полезен при построении динамически масштабируемых систем, таких как чаты, игры, распределённые очереди и др.