Проверить
2 уникальных случая использования GenServer.reply | Deep Insights | Эксперт по эликсирам
Не бойтесь больше отвечать.
В этой статье мы говорим о двух сценариях использования функции ответа в модуле GenServer, объясняя это живыми примерами.
Ниже приведены два варианта использования, которые мы рассмотрим в этой статье.
- Многоузловая связь через серверные сообщения с использованием multi_call
- Преобразование асинхронного запроса в синхронный
О чем эта статья?
Здесь не говорится о GenServers и его использовании. Надеюсь, вы уже знаете о GenServer и его функциях. Если нет, ознакомьтесь со следующими статьями, чтобы освоить GenServer.
Ссылки на статьи для лучшего понимания GenServer
1. Многоузловая связь через зарегистрированный процесс | GenServer.multi_call
Требования к зданию
Прежде чем приступить к фактическому кодированию, нам нужно, чтобы два модуля GenServer были зарегистрированы с тем же именем, но на двух разных узлах.
Давай построим.
Модули GenServer
Следующие ниже файлы GenServer не являются сложными, но упрощены в соответствии с нашими требованиями для демонстрационных целей. Единственная задача - отправить current_balance по запросу с сообщением :show_balance
после ожидания 3
секунд .
bank1.ex
#bank1.ex defmodule Bank1 do use GenServer def start_link(balance) do GenServer.start_link(__MODULE__, balance, name: Bank) end def init(balance) do {:ok, %{balance: balance}} end def handle_call(:show_balance, from, state) do Process.send_after(self(), {:reply, from}, 3_000) {:noreply, state} end def handle_info({:reply, bank_server}, %{balance: balance} = state) do GenServer.reply(bank_server, "The balance from Bank1 #{balance}") {:noreply, state} end end
ban2.ex
#bank2.ex defmodule Bank2 do use GenServer def start_link(balance) do GenServer.start_link(__MODULE__, balance, name: Bank) end def init(balance) do {:ok, %{balance: balance}} end def handle_call(:show_balance, from, state) do Process.send_after(self(), {:reply, from}, 3_000) {:noreply, state} end def handle_info({:reply, bank_server}, %{balance: balance} = state) do GenServer.reply(bank_server, "The balance from Bank2 #{balance}") {:noreply, state} end end
Между этими двумя файлами нет большой разницы, кроме ответного сообщения. Надеюсь, вам будет нелегко получить файлы.
Создание нескольких узлов IEx
Поскольку нам нужно запросить несколько узлов, мы создаем три iex
узла с помощью флага --sname
Откройте свой терминал и создайте три вкладки и выполните следующие команды по одной на каждой вкладке.
$ iex --sname bank1 $ iex --sname bank2 $ iex --sname central_bank
Подключение узлов
Здесь я назвал узлы bank1
, bank2
и central_bank
. Узел central_bank
соединится с двумя другими узлами bank1
и bank2
.
Теперь войдите в свой iex
узел с именем central_bank
и подключитесь к оставшимся узлам, как показано ниже 👇
iex(central_bank@blackode)1> Node.connect :bank1@blackode true iex(central_bank@blackode)2> Node.connect :bank2@blackode true
Node.list
предоставит вам список подключенных узлов, а blackode - это имя моей машины.
iex(central_bank@blackode)3> Node.list [:bank1@blackode, :bank2@blackode]
Посмотрите на следующий снимок экрана: соединение узлов.
Компиляция модулей GenServer
Следующим шагом будет загрузка созданных модулей GenServer из файлов bank1.ex и bank2.ex. Мы загружаем bank1.ex и bank2.ex в iex
сеансах с именами bank1
и bank2
соответственно.
Загрузка Bank1
iex(bank1@blackode)1> c "bank1.ex" [Bank1]
Загрузка Bank2
iex(bank2@blackode)1> c "bank2.ex" [Bank2]
ПРИМЕЧАНИЕ.
Убедитесь, что вы указали правильные пути при компиляции файлов в соответствующих узлах. Я начал сеансы из той же папки, в которой существуют файлы. Итак, я просто передаю имена файлов вместо пути.
Если ваши сеансы и файлы GenServer находятся в разных каталогах, вам необходимо указать точный путь, как показано ниже.
iex> c "file/path/to/yourfile.ex" example c "/home/code/bank1.ex"
Отправка запроса на несколько GenServer на разных узлах | GenServer.multi_call
Поскольку мы уже загрузили модули Bank1 и Bank2 в узлы bank1
и bank2
соответственно, нам необходимо запустить серверы, вызвав функцию start_link
из их узлов независимо, как показано ниже.
iex(bank1@blackode) Bank1.start_link(1000)
В приведенной выше строке кода мы запускаем сервер с начальным балансом 1000 с узла bank1
.
iex(bank2@blackode) Bank2.start_link(2000)
Точно так же мы запускаем сервер с начальным балансом 2000 с узла bank2
.
Вызов функции GenServer.multi_call
Теперь переключитесь на узел central_bank
и отправьте сообщение с помощью multi_call. Нам нужно передать подключенные узлы, зарегистрированное имя сервера (здесь Банк) и сообщение серверу (здесь: show_balance).
iex(central_bank@blackode)> nodes = Node.list iex(central_bank@blackode)> GenSever.multi_call nodes, Bank, :show_balance {[ bank1@blackode: "The balance from Bank1 1000", bank2@blackode: "The balance from Bank2 2000" ], []}
Ответ multi_call
представляет собой кортеж из двух списков {ответы, bad_nodes}
replies
- это список{node, reply}
кортежей, гдеnode
- ответивший узел, аreply
- его ответbad_nodes
- это список узлов, которые либо не существовали, либо сервер с указаннымname
не существовал или не отвечал
Приведенные выше определения скопированы непосредственно из elixir docs
В нашем случае мы получили пустой список вместо bad_nodes, потому что все узлы ответили успешно.
Проверьте следующий снимок экрана выполнения
Что произойдет, если один из узлов не сможет ответить?
Если какой-либо из узлов не ответил, это не повлияет на ответы других узлов. Отказавший узел попадает в категорию bad_nodes в ответе на multi_call {replies, bad_nodes}
.
Теперь мы вручную проверяем это, вызывая исключение из узла bank2
. Итак, добавьте строку кода raise "I won't reply"
в файл bank2.ex
в функцию обратного вызова handle_call. Таким образом, он не может ответить. Мы намеренно вывели из строя сервер.
defmodule Bank2 do use GenServer ... def handle_call(:show_balance, from, state) do raise "I won't reply" Process.send_after(self(), {:reply, from}, 3_000) {:noreply, state} end ... end
Теперь перекомпилируйте bank2.ex
файл внутри узла bank2 c "bank2.ex"
и снова вызовите функцию multi_call
из узла central_bank
.
iex(central_bank@blackode)> GenServer.multi_call nodes, Bank, :show_balance {[bank1@blackode: "The balance from Bank1 1000"], [:bank2@blackode]}
На этот раз вы увидите ответ от узла bank1, а не от узла bank2. bank2
подпадает под bad_nodes
.
Это все о двух вариантах использования GenServer.reply (). Надеюсь, вы поняли использование функции ответа вместо multi_call в GenServer.
2. Преобразование асинхронного запроса в синхронный запрос.
Требование
Предположим, что вам нужно обработать несколько запросов билетов какого-либо типа, и каждый запрос занимает больше времени. Итак, вам нужно освободить сервер, поместив задачу по времени в отдельную задачу (процесс), и нам также нужно отправить ответ после завершения задания по времени.
Здесь мы сосредоточимся на следующих моментах со стороны кодирования.
- Продолжительность выполнения задания асинхронно
- Ответ вызывающему абоненту после завершения длительного задания.
Уловка для кодовой игры
Поскольку нам нужно отправить ответ после того, как работа будет выполнена, мы определенно используем обратный вызов handle_call
, поскольку он содержит информацию об отправителе, которому мы должны ответить. Но мы сохраняем логику учета времени в отдельном процессе, и нам также необходимо освободить обратный вызов. Итак, мы используем {:noreply, state}
вместо обычного {:reply, reply, state}
Но кто тогда отправит ответ, если handle_call не отправляет?
Чтобы ответить отправителю, то есть от которого мы получили запрос, мы поддерживаем состояние сервера, добавляя информацию тикета при создании ссылки и кто просил вроде Map.put (tickets, ref, from)
.
Задание времени, которое мы выполняем в отдельном процессе, будет отчитываться на TicketServer, отправив сообщение {:ticket_processed, ref, response}
, которое мы обрабатываем асинхронно. Итак, мы можем отправить ответ в виде ответа отправителю, выйдя из тикетов из состояния с помощью ключа ref
, например {from, remaining_tickets} = Map.pop(tickets, ref)
, а затем мы используем GenServer.reply
для отправки ответа вызывающему.
Это теория и несколько плавающих строк кода. Давайте погрузимся в реальный код.
#ticket_server.ex defmodule TicketServer do use GenServer def start_link(options \\ []) do GenServer.start_link(__MODULE__, %{tickets: %{}}, options) end def init(state) do {:ok, state} end def process(pid, ticket) do GenServer.call(pid, {:process_ticket, ticket}) end def handle_call({:process_ticket, ticket}, from, %{tickets: tickets} = state) do ref = make_ref() time_taking_job(self(), ref, ticket) tickets = Map.put(tickets, ref, from) state = %{state | tickets: tickets} {:noreply, state} end def handle_cast({:ticket_processed, reference, response}, %{tickets: tickets} = state) do {from, remaining_tickets} = Map.pop(tickets, reference) GenServer.reply(from, response) state = %{state | tickets: remaining_tickets} {:noreply, state} end def time_taking_job(pid, reference, ticket) do Task.start fn -> Process.sleep(3000) GenServer.cast(pid, {:ticket_processed, reference, "Got TicketResp: #{ticket}"}) end IO.puts "#{ticket} has been processing...\n Please wait :)" end end
Кратко о коде
Мы запускаем сервер, вызывая start_link
, после чего получаем pid. После этого мы вызываем функцию process
с pid
и ticket
. Билет здесь представляет собой просто строку. В свою очередь, функция process
запускает GenServer.call с сообщением {:process_ticket, ticket}
. Вызывается сопоставленный обратный вызов handle_call
get, и именно здесь мы разделяем логику времени и отправку ответа.
Теперь давайте запустим сервер.
iex> {:ok, pid} = TicketServer.start_link() #starting iex❯ TicketServer.process pid, "T1234" #calling process T1234 has been processing... Please wait :) "Got TicketResp: T1234" #prints after 3 seconds
Надеюсь, вы поняли, как использовать GenServer.reply()
в соответствии с вашими требованиями.
Удачного кодирования !!
Спасибо за чтение.
Присоединяйтесь к нашему каналу Telegram и поддержите нас.
«Blackoders
EAT 🍕 - CODE🐞 - SLEEP😴 Код, мысли и идеи Ресурсы по кодированию, советы, видео, статьи и новости Мы следим и собираем… t .меня"
Посетите репозиторий GitHub в разделе Советы по Killer Elixir
Рад, если вы внесете свой вклад в ★
TQ!