集群中的全局动态主管

Global Dynamic Supervisor in a cluster

我有一个独特的问题,我不需要在 elxir 中解决。

我需要使用动态主管在集群环境中动态启动 (n) 个子级。我正在使用 libcluster 来管理集群并使用全局进程注册表来查找动态主管 pid。这是正在发生的事情:

global: Name conflict terminating {:packer_supervisor, #PID<31555.1430.0>}

这里是主管的代码:

defmodule EcompackingCore.PackerSupervisor do
  use DynamicSupervisor
  require Logger

  def start_link() do
    DynamicSupervisor.start_link(__MODULE__, :ok, name: {:global, :packer_supervisor})
  end

  def init(:ok) do
    Logger.info("Starting Packer Supervisor")
    DynamicSupervisor.init(strategy: :one_for_one)
  end

  def add_packer(badge_id, packer_name) do
    child_spec = {EcompackingCore.Packer, {badge_id, packer_name}}
    DynamicSupervisor.start_child(:global.whereis_name(:packer_supervisor), child_spec)
  end

  def remove_packer(packer_pid) do
    DynamicSupervisor.terminate_child(:global.whereis_name(:packer_supervisor), packer_pid)
  end

  def children do
    DynamicSupervisor.which_children(:global.whereis_name(:packer_supervisor))
  end

  def count_children do
    DynamicSupervisor.count_children(:global.whereis_name(:packer_supervisor))
  end

end

问题似乎是主管在两个节点上都启动了。处理这个问题的最佳方法是什么?我真的需要主管是动态的,这样我才能有效地管理工作模块。可能是不同的注册表?

感谢您的帮助。

经过一番研究,我找到了解决方案:

我现在正在使用 https://github.com/bitwalker/swarm 来处理 pid 注册。这允许跨集群设置进程,并在其中一个节点出现故障时提供切换支持。

可以用一个简单的中心节点来监控其他节点,当然是一个supervisor。

这个中心节点只做启动,monitor,并使用数据库保存其他节点的状态和pid。

当一个节点加入和宕机时,您可以接收它的宕机消息并处理它(更新数据库)。

这种方式唯一的缺点就是只能有一个中心节点,但是这个节点做的事情简单,基本稳定,在我们的生产系统上运行了一年。

如果您想要与全局进程注册表一起使用的相当简单的解决方案,您可以更改动态主管 start_link

defmodule EcompackingCore.PackerSupervisor do
  use DynamicSupervisor
  require Logger

  def start_link() do
    case DynamicSupervisor.start_link(__MODULE__, :ok, name: {:global, :packer_supervisor}) do
      {:ok, pid} ->
        {:ok, pid}
      {:error, {:already_started, pid}} ->
        # you need this pid so on each node supervisor 
        # of this dynamic supervisor can monitor this same pid
        # so each node tracks existence of your process
        {:ok, pid}
      any -> any
    end
  end

  def init(:ok) do
    Logger.info("Starting Packer Supervisor")
    DynamicSupervisor.init(strategy: :one_for_one)
  end

  def add_packer(badge_id, packer_name) do
    child_spec = {EcompackingCore.Packer, {badge_id, packer_name}}
    DynamicSupervisor.start_child(:global.whereis_name(:packer_supervisor), child_spec)
  end

  def remove_packer(packer_pid) do
    DynamicSupervisor.terminate_child(:global.whereis_name(:packer_supervisor), packer_pid)
  end

  def children do
    DynamicSupervisor.which_children(:global.whereis_name(:packer_supervisor))
  end

  def count_children do
    DynamicSupervisor.count_children(:global.whereis_name(:packer_supervisor))
  end
end

关于这个解决方案很多人会说你不应该这样做,因为在网络分裂的情况下,你最终会在集群中有两个或更多的全局进程。但是如果你实现一些节点 monitoring/tracker 你甚至可以处理这个问题,这样你就知道集群中有多少节点 "see" 了。

例如,如果集群大小为 5,那么您可以创建检查规则来检查您是否看到 3 个以上的节点,如果没有,那么您将安排下一次启动,例如 1 秒并尝试在全局范围内再次注册您的dynamic supervisor until check rule returns true (意味着你在多数群体中,你可以在该群体中提供一致性)。另一方面,如果你的节点是少数群体,并且已经持有全局动态主管,则将其关闭并在 1 秒后开始调度。

这是实现集群一致性的最简单方法,但您应该考虑一件事。这个 Dynamic Supervisor 将在单个节点上启动 worker,我相信你不想这样做,所以宁愿使用全局注册表和一些负载平衡算法来平衡应该在本地 supervisor 中启动的进程。

  • Swarm 具有内置环和静态仲裁环算法,但它使用散列来跨集群分配负载。如果您的工作人员具有可以计算哈希值的 ID,这是一个很好的解决方案。
  • Syn 是另一种选择。