Erlang:动态节点集上的作业调度

时间:2011-02-16 11:10:07

标签: erlang scheduled-tasks

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

一个例子:

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

问题:

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

以下是一些问题:

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

非常欢迎任何建议!

2 个答案:

答案 0 :(得分:4)

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

  • 产生100k进程对Erlang来说并不是什么大问题,因为产生进程要比其他系统便宜得多。

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

  • 不要打扰处理作业并等待新作业的工作进程。如果你使用OS进程或线程,这是要走的路,因为产生很昂贵,但在Erlang中这只会增加不必要的复杂性。

pool工具作为低级构建块非常有用,它唯一错过了您的功能就是能够自动启动其他节点。我要做的是从池和一组固定的节点开始,以获得基本功能。

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

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

通过这种方式,您可以快速pool控制传入作业的分布,以及一种更慢的完全独立的添加和删除节点的方式。

如果你完成了所有这些工作并且仍然发现 - 在经过一些真实的基准测试之后 - 你需要重新平衡你可以随时在作业主循环中建立某些东西的工作,在消息rebalance之后它可以重新生成本身使用池主服务器将其当前状态作为参数传递。

最重要的是继续构建简单易用的工具,并在以后进行优化。

答案 1 :(得分:1)

我对问题的解决方案:

“distributor” - gen_server, “工人” - gen_server。

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

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

虽然这不是一组动态节点,但您可以随时从分销商处开始新节点。

P.S。看起来类似于池,但在我的情况下,我提交端口进程,所以我需要限制它们并更好地控制将要发生的事情。