Я бы пять узлов: pending_tasks
, completed_tasks
, running_tasks
, workers
и queues
. pending_tasks
- это узел, который содержит задачи, как новые, так и те, которые будут перезапускаться из-за сбоев в рабочих узлах. completed_tasks
содержит детали выполненных заданий. running_tasks
выполняет задачи, которые назначаются работникам. В реализации PoC я сделал, как только я использовал POJO, закодированный в XML, для хранения сведений о задачах. Узлы в pending_tasks
, completed_tasks
и running_tasks
- все постоянные узлы.
workers
содержит эфемерные узлы, которые представляют доступные рабочие. Учитывая, что они являются эфемерными, эти узлы сигнализируют об отказах рабочих. queues
напрямую связан с workers
: есть узел в queues
для каждого узла в workers
. Узлы в queues
используются для выполнения задач, назначенных для каждого из рабочих.
Теперь вам нужен мастер. Мастер отвечает за три вещи: i) смотреть pending_tasks
для новых задач; ii) смотреть workers
, чтобы зарегистрировать новый queues
, когда прибудут новые рабочие, и вернуть задания в pending_tasks
, когда рабочие пропали; и iii) опубликовать результат задач в completed_tasks
(когда я это сделал, результат будет проходить через механизм уведомления публикации/подписки). Кроме того, мастер должен выполнить некоторую очистку при запуске, учитывая, что работники могут выйти из строя во время простоя мастера.
Мастер алгоритм заключается в следующем:
at (start-up) {
for (q -> /queues) {
if q.name not in nodesOf(/workers) {
for (t -> nodesOf(/queues/d.name)) {
create /pending_tasks/t.name
delete /running_tasks/t.name
delete /queues/d.name/t.name
}
delete /queues/d.name
}
}
for (t -> nodesOf(/completed_tasks)) {
publish the result
deleted /completed_tasks/c.name
}
}
watch (/workers) {
case c: Created => register the new worker queue
case d: Deleted => transaction {
for (t -> nodesOf(/queues/d.name)) {
create /pending_tasks/t.name
delete /running_tasks/t.name
delete /queues/d.name/t.name
}
delete /queues/d.name
}
}
watch (/pending_tasks) {
case c: Created => transaction {
create /running_tasks/c.name
create persistent node in one of the workers queue (eg, /queues/worker_0/c.name)
delete /pending_tasks/c.name
}
}
watch (/completed_tasks) {
case c: Created =>
publish the result
deleted /completed_tasks/c.name
}
Алгоритм работника заключается в следующем:
at (start-up) {
create /queue/this.name
create a ephemeral node /workers/this.name
}
watch (/queue/this.name) {
case c: Created =>
perform the task
transaction {
create /completed_tasks/c.name with the result
delete /queues/this.name/c.name
delete /running_tasks/c.name
}
}
Некоторые замечания о том, когда я думал об этом проекте. Во-первых, в любой момент времени не выполнялись задачи, нацеленные на одни и те же вычисления. Поэтому я назвал задачи после проведенных вычислений. Таким образом, если два разных клиента запросили одни и те же вычисления, только один будет успешным, поскольку только один сможет создать узел /pending_tasks
. Аналогично, если задача уже запущена, создание узла /running_task/
потерпит неудачу, и никакая новая задача не будет отправлена.
Во-вторых, могут быть произвольные сбои как у мастеров, так и у рабочих, и никакая задача не будет потеряна.Если рабочий не работает, просмотр событий удаления в /worker
вызовет переназначение задач. Если мастер терпит неудачу, и любое заданное число рабочих провалится до того, как новый мастер будет установлен, процедура запуска переместит задачи обратно на /pending_tasks
и опубликует любой ожидающий результата.
В-третьих, я, возможно, забыл какой-то угловой корпус, так как у меня нет доступа к этой реализации PoC. Я буду рад обсудить любую проблему.
Есть ли зависимость между различными задачами? –
@OferLando Нет, между различными задачами нет зависимости. Они независимы друг от друга. – john
Посмотрите на этот проект, который я написал. Он делает что-то подобное. Вы могли бы использовать его напрямую или в качестве примера: https://github.com/NirmataOSS/workflow – Randgalt