如何分配受监督的 gen_server 工人?
How to distribute supervised gen_server workers?
您好,我想练习实现分布式缓存。缓存模块基于gen_server。缓存由 CacheSupervisor 模块启动。起初我在一个节点上尝试 运行 ,效果很好。现在我正在尝试将我的缓存分布在两个节点上,这两个节点位于我笔记本电脑上的两个打开的控制台 windows 中。
PS:
在写这个问题时,我意识到我忘记将我的第三个 window 连接到其他节点。我修复了它,但我仍然有原来的错误。
控制台:
连接我的节点后,我在第三个 window 中调用 CacheSupervisor.start_link()
,这导致以下错误消息。
错误:
** (EXIT from #PID<0.112.0>) shutdown: failed to start child: :de
** (EXIT) an exception was raised:
** (ArgumentError) argument error
erlang.erl:2619: :erlang.spawn(:node1@DELL_XPS, {:ok, #PID<0.128.0>})
(stdlib) supervisor.erl:365: :supervisor.do_start_child/2
(stdlib) supervisor.erl:348: :supervisor.start_children/3
(stdlib) supervisor.erl:314: :supervisor.init_children/2
(stdlib) gen_server.erl:328: :gen_server.init_it/6
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
我猜测错误表明我的缓存模块 start_link(name)
内的 :gen_server.start_link(..)
解析为 {:ok, #PID<0.128.0>}
这似乎是不正确的,但我不知道在哪里放 Node.spawn()
else
模块缓存:
defmodule Cache do
use GenServer
def handle_cast({:put, url, page}, {pages, size}) do
new_pages = Dict.put(pages, url, page)
new_size = size + byte_size(page)
{:noreply, {new_pages, new_size}}
end
def handle_call({:get, url}, _from, {pages, size}) do
{:reply, pages[url], {pages, size}}
end
def handle_call({:size}, _from, {pages, size}) do
{:reply, size, {pages, size}}
end
def start_link(name) do
IO.puts(elem(name,0))
Node.spawn(String.to_atom(elem(name, 0)), :gen_server.start_link({:local,elem(name, 1)}, __MODULE__, {HashDict.new, 0}, []))
end
def put(name, url, page) do
:gen_server.cast(name, {:put, url, page})
end
def get(name, url) do
:gen_server.call(name, {:get, url})
end
def size(name) do
:gen_server.call(name, {:size})
end
end
模块 CacheSupervisor:
defmodule CacheSupervisor do
use Supervisor
def init(_args) do
workers = Enum.map( [{"node1@DELL_XPS", :de},{"node1@DELL_XPS", :edu}, {"node2@DELL_XPS", :com} ,{"node2@DELL_XPS", :it}, {"node2@DELL_XPS", :rest}],
fn(n)-> worker(Cache, [n], id: elem(n, 1)) end)
supervise(workers, strategy: :one_for_one)
end
def start_link() do
:supervisor.start_link(__MODULE__, [])
end
end
:erlang.spawn(:node1@DELL_XPS, {:ok, #PID<0.128.0>})
:erlang.spawn/2
与Node.spawn/2
的功能相同。该函数需要节点名称(您已提供)和一个函数。您的 GenServer.start_link 调用返回了 {:ok, Pid} 它应该的。由于不能将元组视为函数 Node.spawn/2
崩溃。
我不建议像这样在单独的节点上生成进程。如果远程节点出现故障,您不仅会在集群中丢失该节点,而且还必须处理所有衍生进程的后果。这将导致应用程序比其他方式更脆弱。如果你想让你的缓存 GenServers 运行 在多个节点上,我建议 运行 你在多个节点上构建的应用程序,并在每个节点上有一个 CacheSupervisor
的实例。然后每个 CacheSupervisor
启动它自己的 GenServers。这更健壮,因为如果一个节点出现故障,其余节点将不受影响。当然,您的应用程序逻辑需要考虑到这一点,丢失节点可能意味着丢失缓存数据或客户端连接。有关详细信息,请参阅此答案:How does an Erlang gen_server start_link a gen_server on another node?
如果你真的很想像这样在远程节点上生成进程,你可以这样做:
:erlang.spawn_link(:node1@DELL_XPS, fun() ->
{:ok, #PID<0.128.0>} = :gen_server.start_link({:local,elem(name, 1)}, __MODULE__, {HashDict.new, 0}, [])
receive
% Block forever
:exit -> :ok
end
end)
请注意,您必须使用 spawn_link,因为主管希望链接到他们的 children。如果主管没有链接,它将不知道 child 何时崩溃并且无法重新启动进程。
您好,我想练习实现分布式缓存。缓存模块基于gen_server。缓存由 CacheSupervisor 模块启动。起初我在一个节点上尝试 运行 ,效果很好。现在我正在尝试将我的缓存分布在两个节点上,这两个节点位于我笔记本电脑上的两个打开的控制台 windows 中。
PS:
在写这个问题时,我意识到我忘记将我的第三个 window 连接到其他节点。我修复了它,但我仍然有原来的错误。
控制台:
连接我的节点后,我在第三个 window 中调用 CacheSupervisor.start_link()
,这导致以下错误消息。
错误:
** (EXIT from #PID<0.112.0>) shutdown: failed to start child: :de
** (EXIT) an exception was raised:
** (ArgumentError) argument error
erlang.erl:2619: :erlang.spawn(:node1@DELL_XPS, {:ok, #PID<0.128.0>})
(stdlib) supervisor.erl:365: :supervisor.do_start_child/2
(stdlib) supervisor.erl:348: :supervisor.start_children/3
(stdlib) supervisor.erl:314: :supervisor.init_children/2
(stdlib) gen_server.erl:328: :gen_server.init_it/6
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
我猜测错误表明我的缓存模块 start_link(name)
内的 :gen_server.start_link(..)
解析为 {:ok, #PID<0.128.0>}
这似乎是不正确的,但我不知道在哪里放 Node.spawn()
else
模块缓存:
defmodule Cache do
use GenServer
def handle_cast({:put, url, page}, {pages, size}) do
new_pages = Dict.put(pages, url, page)
new_size = size + byte_size(page)
{:noreply, {new_pages, new_size}}
end
def handle_call({:get, url}, _from, {pages, size}) do
{:reply, pages[url], {pages, size}}
end
def handle_call({:size}, _from, {pages, size}) do
{:reply, size, {pages, size}}
end
def start_link(name) do
IO.puts(elem(name,0))
Node.spawn(String.to_atom(elem(name, 0)), :gen_server.start_link({:local,elem(name, 1)}, __MODULE__, {HashDict.new, 0}, []))
end
def put(name, url, page) do
:gen_server.cast(name, {:put, url, page})
end
def get(name, url) do
:gen_server.call(name, {:get, url})
end
def size(name) do
:gen_server.call(name, {:size})
end
end
模块 CacheSupervisor:
defmodule CacheSupervisor do
use Supervisor
def init(_args) do
workers = Enum.map( [{"node1@DELL_XPS", :de},{"node1@DELL_XPS", :edu}, {"node2@DELL_XPS", :com} ,{"node2@DELL_XPS", :it}, {"node2@DELL_XPS", :rest}],
fn(n)-> worker(Cache, [n], id: elem(n, 1)) end)
supervise(workers, strategy: :one_for_one)
end
def start_link() do
:supervisor.start_link(__MODULE__, [])
end
end
:erlang.spawn(:node1@DELL_XPS, {:ok, #PID<0.128.0>})
:erlang.spawn/2
与Node.spawn/2
的功能相同。该函数需要节点名称(您已提供)和一个函数。您的 GenServer.start_link 调用返回了 {:ok, Pid} 它应该的。由于不能将元组视为函数 Node.spawn/2
崩溃。
我不建议像这样在单独的节点上生成进程。如果远程节点出现故障,您不仅会在集群中丢失该节点,而且还必须处理所有衍生进程的后果。这将导致应用程序比其他方式更脆弱。如果你想让你的缓存 GenServers 运行 在多个节点上,我建议 运行 你在多个节点上构建的应用程序,并在每个节点上有一个 CacheSupervisor
的实例。然后每个 CacheSupervisor
启动它自己的 GenServers。这更健壮,因为如果一个节点出现故障,其余节点将不受影响。当然,您的应用程序逻辑需要考虑到这一点,丢失节点可能意味着丢失缓存数据或客户端连接。有关详细信息,请参阅此答案:How does an Erlang gen_server start_link a gen_server on another node?
如果你真的很想像这样在远程节点上生成进程,你可以这样做:
:erlang.spawn_link(:node1@DELL_XPS, fun() ->
{:ok, #PID<0.128.0>} = :gen_server.start_link({:local,elem(name, 1)}, __MODULE__, {HashDict.new, 0}, [])
receive
% Block forever
:exit -> :ok
end
end)
请注意,您必须使用 spawn_link,因为主管希望链接到他们的 children。如果主管没有链接,它将不知道 child 何时崩溃并且无法重新启动进程。