【问题标题】:How to distribute supervised gen_server workers?如何分配受监督的 gen_server 工作人员?
【发布时间】:2017-04-17 20:25:08
【问题描述】:

您好,我想实现分布式缓存作为练习。缓存模块基于 gen_server。缓存由 CacheSupervisor 模块启动。起初我尝试在一个节点上运行它,效果很好。现在我正在尝试将我的缓存分布在两个节点上,这些节点位于我笔记本电脑上的两个打开的控制台窗口中。

PS:

在写这个问题时,我意识到我忘记将第三个窗口连接到其他节点。我修复了它,但我仍然遇到原来的错误。

控制台:

连接我的节点后,我在第三个窗口中调用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&lt;0.128.0&gt;},这似乎不正确,但我不知道将Node.spawn() 放在哪里

模块缓存:

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 elixir gen-server


    【解决方案1】:
    :erlang.spawn(:node1@DELL_XPS, {:ok, #PID<0.128.0>})
    

    :erlang.spawn/2Node.spawn/2 功能相同。该函数需要节点名称(您已提供)和一个函数。您的 GenServer.start_link 调用应返回 {:ok, Pid} 。由于不能将元组视为函数Node.spawn/2 崩溃。

    我不建议像这样在单独的节点上生成进程。如果远程节点出现故障,您不仅会在集群中丢失该节点,而且您还必须处理所有衍生进程的后果。这将导致应用程序比其他方式更脆弱。如果您想让您的缓存 GenServers 在多个节点上运行,我建议您在多个节点上运行您正在构建的应用程序,并在每个节点上都有一个 CacheSupervisor 的实例。然后每个CacheSupervisor 在它下面启动它自己的GenServer。这更加健壮,因为如果一个节点发生故障,其余节点将不受影响。当然,您的应用程序逻辑需要考虑到这一点,丢失节点可能意味着丢失缓存数据或客户端连接。有关详细信息,请参阅此答案: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,因为主管希望链接到他们的孩子。如果主管未链接,它将不知道孩子何时崩溃并且将无法重新启动进程。

    【讨论】:

    • 谢谢,您的回答很有帮助。我决定修改程序逻辑。现在每个工人都有自己的主管
    猜你喜欢
    • 2018-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-14
    • 2012-12-17
    • 2015-12-07
    • 1970-01-01
    • 2014-05-19
    相关资源
    最近更新 更多