【问题标题】:Erlang: Job Scheduling Over a Dynamic Set of NodesErlang:在一组动态节点上进行作业调度
【发布时间】:2011-06-28 06:59:10
【问题描述】:

我需要一些建议,在 Erlang 中编写一个作业调度程序,它能够在一组工作节点上分配作业(外部操作系统进程)。一项工作可以持续几毫秒到几个小时。 “调度程序”应该是一个全局注册表,作业进入,排序,然后在连接的“工作节点”上分配和执行。工作节点应该能够通过告诉他们能够并行处理多少个作业(插槽)来在调度程序上注册。工作节点应该能够随时加入和离开。

一个例子:

  • 调度程序有 10 个作业等待
  • 工作节点 A 连接并能够并行处理 3 个作业
  • 工作节点 B 连接并能够并行处理 1 个作业
  • 一段时间后,另一个工作节点加入,能够并行处理 2 个作业

问题:

我认真地花了一些时间思考这个问题,但我仍然不确定该走哪条路。我目前的解决方案是为调度程序提供一个全局注册的 gen_server ,该调度程序将作业保持在其状态。每个工作节点产生 N 个工作进程并将它们注册到调度程序上。然后,工作进程从调度程序中拉出一个作业(如果当前没有可用的作业,这是一个无限阻塞调用 {noreply, ...})。

这里有一些问题:

  • 知道在新工作人员连接时我必须将工作重新分配给另一个工作人员,将每个新工作分配给现有工作人员是否是个好主意? (我认为这就是 Erlang SMP 调度程序的工作方式,但重新分配作业对我来说似乎很头疼)
  • 我应该为每个工作程序处理槽启动一个进程吗?这个进程应该在哪里运行:在调度程序节点上还是在工作程序节点上?调度程序应该对工作节点进行 rpc 调用,还是让工作节点拉取新作业然后自己执行它们会更好?
  • 最后:这个问题已经解决了吗?在哪里可以找到它的代码? :-) 我已经尝试过使用 RabbitMQ 进行作业调度,但是自定义作业排序和部署增加了很多复杂性。

非常欢迎任何建议!

【问题讨论】:

  • 您是否已经考虑过pool(3) 设施?它们可用于动态分配负载和添加节点。请参阅stackoverflow.com/questions/4853750/… 或者它与您正在寻找的完全不同?
  • 是的,已经考虑过了。但不知道 pool 在这里有什么帮助。如果我对池的理解正确,那么同时进来的 10 万个传入作业将在所有已注册的工作节点上产生 10 万个进程。但是这样我就不能限制并行计算的作业数量,并且添加额外的节点来处理作业负载不会重新平衡作业。
  • 这些“工作”要在 Erlang 中实现吗?
  • "job" 在我的例子中是指一个可以持续几毫秒到几个小时的外部操作系统进程。

标签: erlang scheduled-tasks


【解决方案1】:

我的解决方法:

“分销商” - gen_server, “工人” - gen_server。

“distributor”使用slave:start_link启动“workers”,每个“worker”都使用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

事实上,它比这更复杂,因为我需要跟踪正在运行的作业,如果需要,可以杀死它们,但在这样的架构中很容易实现。

虽然这不是一组动态节点,但您可以在需要时从分发服务器启动新节点。

附:看起来类似于池,但在我的情况下,我正在提交端口进程,因此我需要限制它们并更好地控制去往哪里。

【讨论】:

  • “提交端口进程”是什么意思?
  • 我的意思是“Ruby 进程”作为“工作”,更具体的说是“Rails”。
  • 我是否遗漏了问题中的某些内容?我读到这些工作是 Erlang 唯一的事务。这将对解决方案的偏好产生很大影响。
  • 抱歉,我没有解释“作业”是什么意思。作业是外部操作系统端口进程,持续时间从几毫秒到几小时不等。
【解决方案2】:

在 cmets 中阅读了您的答案后,我仍然建议使用 pool(3)

  • 生成 10 万个进程对 Erlang 来说没什么大不了的,因为生成一个进程比在其他系统中便宜得多。

  • 每个作业一个进程是 Erlang 中一个非常好的模式,启动一个新进程在进程中运行作业,保持进程中的所有状态,并在作业完成后终止进程。

  • 不要打扰处理作业并等待新作业的工作进程。如果您使用的是 OS 进程或线程,这是可行的方法,因为生成成本很高,但在 Erlang 中这只会增加不必要的复杂性。

pool 工具作为一个低级构建块很有用,它唯一缺少的功能是自动启动其他节点的能力。我要做的是从池和一组固定的节点开始获得基本功能。

然后添加一些额外的逻辑来监视节点上的负载,例如也喜欢池使用statistics(run_queue)。如果您发现所有节点都超过了某个负载阈值,只需 slave:start/2,3 在额外的机器上添加一个新节点,然后使用 pool:attach/1 将其添加到您的池中。

这不会重新平衡旧的正在运行的作业,但新作业会自动移动到新启动的节点,因为它仍然处于空闲状态。

有了这个,您可以有一个快速的pool 受控分配传入作业和一个较慢的完全独立的添加和删除节点的方式。

如果你完成了所有这些工作并且仍然发现——请在进行一些真实世界的基准测试之后——你需要重新平衡工作,你总是可以在工作主循环中构建一些东西,在消息 rebalance 之后它可以使用池主将其当前状态作为参数传递。

最重要的是继续构建一些简单且有效的东西,然后再对其进行优化。

【讨论】:

    猜你喜欢
    • 2016-04-23
    • 2012-08-23
    • 1970-01-01
    • 2022-04-12
    • 2020-09-27
    • 1970-01-01
    • 2011-07-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多