Глобальный динамический супервизор в кластере

У меня есть уникальная проблема, с которой мне не нужно было обращаться в 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

Проблема заключается в том, что супервизор запускается на обоих узлах. Какой был бы лучший способ справиться с этим? Мне действительно нужен супервизор, чтобы быть динамичным, чтобы я мог эффективно управлять рабочими модулями. Возможно, другой реестр?

Спасибо за вашу помощь.

Ответ 1

После небольшого исследования я нашел решение:

Теперь я использую https://github.com/bitwalker/swarm для обработки регистрации pid. Это позволяет настраивать процессы в кластере и предлагает поддержку от руки, если один из узлов опускается.

Ответ 2

Вы можете использовать простой центральный узел для контроля других узлов, конечно, одного супервизора.

Этот центральный узел только запускает, контролирует и использует базу данных для сохранения статуса других узлов и pid.

Когда узел соединяется и опускается, вы можете получить его сообщение вниз и обработать его (обновить базу данных).

Единственным недостатком этого метода является то, что у вас может быть только один центральный узел, но поскольку этот узел выполняет простые вещи, он почти стабилен, который работает на нашей производственной системе в течение года.

Ответ 3

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

Многие люди скажут об этом решении, что вы не должны этого делать, поскольку в случае разделения сети вы получаете два или более глобальных процесса в кластере. Но вы даже можете справиться с этим, если вы реализуете некоторый мониторинг/отслеживание узлов, чтобы вы знали, сколько узлов вы видите в кластере.

Например, если размер кластера iz 5, вы можете создать правило проверки, которое будет проверять, если вы увидите узлы 3+, если нет, то вы планируете следующий старт, скажем 1 секунду, и попробуйте снова зарегистрировать глобальный динамический супервизор до тех пор, пока не проверите правило возвращает true (это означает, что вы находитесь в мажоритарной группе, и вы можете обеспечить согласованность в этой группе). С другой стороны, если ваш узел является группой меньшинства и уже имеет глобальный динамический супервизор, тогда выключите его и начните расписание за 1 секунду.

Это самый простой способ добиться согласованности между кластерами, но есть одна вещь, которую вы должны учитывать. Этот динамический супервизор запустит рабочих на одном узле, который, я уверен, вы не хотите, скорее используйте глобальный реестр и некоторый алгоритм балансировки нагрузки, чтобы сбалансировать процессы, которые должны запускаться в локальных супервизорах.

  • У Роя есть встроенные кольца и алгоритмы статического кворума, но он использует хеширование для распределения нагрузки между кластерами. Это хорошее решение, если у ваших работников есть ID, для которого вы можете рассчитать хэш.
  • Syn - еще одна альтернатива.