Erlang: планирование заданий для динамического набора узлов - PullRequest
4 голосов
/ 16 февраля 2011

Мне нужен совет по написанию планировщика заданий на Erlang, который может распределять задания (внешние процессы os) по набору рабочих узлов.Работа может длиться от нескольких миллисекунд до нескольких часов.«Планировщик» должен представлять собой глобальный реестр, в который входят задания, которые сортируются, а затем назначаются и выполняются на подключенных «рабочих узлах».Рабочие узлы должны иметь возможность регистрироваться в планировщике, сообщая, сколько заданий они могут обрабатывать параллельно (слоты).Рабочие узлы должны иметь возможность присоединяться и выходить в любое время.

Пример:

  • Планировщик имеет 10 ожидающих заданий
  • Рабочий узел A подключается и может обрабатывать 3 задания параллельно
  • Рабочий узелB подключается и может обрабатывать 1 задание параллельно
  • Через некоторое время присоединяется другой рабочий узел, который может обрабатывать 2 задания параллельно

Вопросы:

Я серьезно провел некоторое время, думая о проблеме, но я все еще не уверен, какой путь пойти.Мое текущее решение состоит в том, чтобы иметь глобально зарегистрированный gen_server для планировщика, который хранит задания в своем состоянии.Каждый рабочий узел порождает N рабочих процессов и регистрирует их в планировщике.Затем рабочие процессы извлекают задание из планировщика (это бесконечный блокирующий вызов с {noreply, ...}, если в данный момент нет доступных заданий).

Вот несколько вопросов:

  • Целесообразно ли назначать каждую новую работу существующему работнику, зная, что мне придется переназначать работу другому работнику в момент подключения новых работников?(Я думаю, что так работает планировщик Erlang SMP, но переназначение заданий кажется мне большой головной болью)
  • Должен ли я запускать процесс для каждого слота обработки работника и где этот процесс должен жить: в планировщикеузел или рабочий узел?Должен ли планировщик выполнять вызовы rpc рабочего узла или было бы лучше, если бы рабочие узлы извлекали новые задания и затем выполняли их самостоятельно?
  • И наконец: уже решена ли эта проблема и где найтикод для этого?:-) Я уже пробовал RabbitMQ для планирования заданий, но сортировка и развертывание пользовательских заданий усложняют задачу.

Любой совет приветствуется!

Ответы [ 2 ]

4 голосов
/ 16 февраля 2011

Прочитав ваш ответ в комментариях, я все равно рекомендую использовать pool(3):

  • Создание 100 тыс. Процессов не является большой проблемой для Эрланга, потому что порождение процесса намного дешевле, чем в других системах.

  • Один процесс на задание - это очень хороший шаблон в Erlang, запустите новый процесс, запустите задание в процессе, сохраняя все состояние процесса, и завершите процесс после завершения задания.

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

Средство pool полезно в качестве низкоуровневого строительного блока, единственное, чего не хватает вашей функциональности, это возможность автоматического запуска дополнительных узлов. Я бы начал с пула и фиксированного набора узлов, чтобы получить базовую функциональность.

Затем добавьте дополнительную логику, которая отслеживает нагрузку на узлы, например, также как пул делает это с statistics(run_queue). Если вы обнаружите, что все узлы превышают определенный порог загрузки, просто slave:start/2,3 новый узел на дополнительной машине и используйте pool:attach/1, чтобы добавить его в свой пул.

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

С этим вы можете иметь быстрое pool контролируемое распределение входящих заданий и более медленный полностью отдельный способ добавления и удаления узлов.

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

Самое главное - просто создать что-то простое и работающее, а затем оптимизировать его.

1 голос
/ 16 февраля 2011

Мое решение проблемы:

"Дистрибьютор" - gen_server, "рабочий" - gen_server.

«Дистрибьютор» запускает «работников», используя slave: start_link, каждый «работник» запускается с параметром max_processes,

"distributor" behavior:

handle_call(submit,...)
  * put job to the queue,
  * cast itself check_queue

handle_cast(check_queue,...)
  * gen_call all workers for load (current_processes / max_processes),
  * find the least busy,
  * if chosen worker load is < 1 gen_call(submit,...) worker 
      with next job if any, remove job from the queue,

"worker" behavior (trap_exit = true):

handle_call(report_load, ...)
  * return current_process / max_process,

handle_call(submit, ...)
  * spawn_link job,

handle_call({'EXIT', Pid, Reason}, ...)
  * gen_cast distributor with check_queue

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

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

P.S. Похоже на пул, но в моем случае я отправляю процессы порта, поэтому мне нужно ограничить их и лучше контролировать, что и куда происходит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...