多机任务分配机制

栏目: 后端 · 发布时间: 5年前

内容简介:假设我们有一个集群,用于处理一系列不同的任务,这时候我们需要对任务进行的一定的分配,使得集群中的每台机器都负责一部分任务。一般来说会有如下几个要求:在这种场景下,该如何设计任务的分配方案?

假设我们有一个集群,用于处理一系列不同的任务,这时候我们需要对任务进行的一定的分配,使得集群中的每台机器都负责一部分任务。

一般来说会有如下几个要求:

  • 一个任务最多只能被执行一次(换言之,只能被分配到一台机器上)
  • 执行任务时集群中每台机器的负载能够保持平衡

在这种场景下,该如何设计任务的分配方案?

为了方便后续的展开,先约束一些表达:

Source
Cluster
Task
Worker

四者之间的关系可以用下图表示:

多机任务分配机制

思路1:简单取模分配

最简单,但也是非常有效的方案,在进行任务分配前需要提前 确定机器数量N ,为每个任务进行编号(或直接使用其id),同时为每个执行任务的机器实例进行编号(0,1,2...)。

即使用下面的公式:

Worker = TaskId % Cluster.size()
复制代码

如果任务没有id标识,那么可以通过随机数的方式来分配任务,在任务数量足够多的情况下,可以保证分配的均衡性(dts网格任务的默认规则就是这么实现的),即:

Worker = random.nextInt() % Cluster.size()
复制代码

简单取模分配的优点是足够简单,虽然负载均衡的效果比较粗糙,但可以很快达到想要的效果,在做紧急任务分机分流的时候比较有用。但从长期上看,需要维护机器数量N的实时更新和推送,并且在机器数量发生变动的时候,可能会出现集群内部的短暂不一致,如果业务对这个比较敏感,还需要进一步优化。

多机任务分配机制

思路2:分布式锁控制系统

为了达到“每个任务只被一台机器执行”的目标,可以考虑使用分布式锁机制,当有多个Worker去消费Task时,只有第一个争抢到锁的Worker才能够执行该Task。

理论上讲,每次抢到锁的Worker都是随机的,那么也就近似的实现了负载均衡;在有成熟中间件依赖的前提下,实现一个分布式锁也并不难(可以借助缓存系统的并发控制实现),并且不用考虑机器数量变化的问题。

多机任务分配机制

但这个方案也有着很多的缺陷,首先争抢锁的过程本身就会消耗Worker的资源,另外由于无法预测究竟哪个Worker能够争抢到Task的锁,所以基本不能保证整个集群的负载均衡。

我个人认为这种方案只适合于内容非常简单、数量比较多,同时执行频率非常高的任务分发(类比多线程读写缓存的场景)。

思路3:中心路由调度

如果要做到比较精细的负载均衡,那么最好的方式就是根据集群的状态、以及任务本身的特性去量身定制一套任务分配的规则,然后通过一个中心的路由层来实现任务的调度,即:

  • Source将任务发送给Router
  • Router根据规则进行决策,并将Task调度到某台Worker
  • (如果任务需要返回结果)Router将对应Worker返回的结果转发给Source
多机任务分配机制

一个简单可行的分配规则是在调度前,计算Worker的CPU、内存等负载,计算一个权重,选择压力最小的机器去运行任务;再进一步可以根据任务本身的复杂度做更精细的拆分。

该方案最大的问题在于,自主去实现一个路由层的成本比较高,另外有出现单点问题的风险(如果路由层挂了,整个任务调度就全部瘫痪了)。

思路4:基于消息队列

这个是类比之前看到的,基于消息队列的分布式数据库解决方案(原文),借助一个可靠的Broker,我们可以很容易构建出一个生产者-消费者模型。

多机任务分配机制

Source产出的Task将全部投入消息队列中,下游的Worker接收Task,并执行(消费)。这样的好处是减少了阻塞,同时可以根据Worker的执行结果,配置重试策略(如果执行失败,再次放回到队列中)。但单单依赖Broker做任务分发的话,并不能解决我们开头的两个问题,因此还需要:

  1. 防止消息被重复消费的机制

    因为绝大多数的消息队列Broker的传输逻辑都是“保证消息至少被送达一次”,所以很有可能出现某个Task被多个Worker获取到的现象,如果要确保“每个任务都只被执行一次”,那么这时候可能需要引入一下上面提到的锁机制来防止重复消费。

    不过如果你选择NSQ作为Broker的话,就不用考虑这个问题。NSQ的特性保证了某个消息在同一个channel下,一定只能被一个消费者消费。

  2. 任务分发

    构建了生产者-消费者模型后,依然不好回答“哪个Task要在哪个Worker上运行”,也就是任务分发的机制,本质上还是依赖于消费者消费动作的随机性,如果要做更精细的调控,大致想一下有两种方案。

    一是在放入队列前就根据所需规则计算好映射关系,然后对Task做一下标记,最后Worker可以设置成只对含有特定标记的Task生效,或者根据Task的标记做不同Topic来分发。

    而是在取出队列的时候再进行计算,这样的话可能下游又需要维护一个路由层来做转发,感觉有些得不偿失。

    就大多数实际情况而言,依赖Broker本身的消息分发机制即可。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

垃圾回收的算法与实现

垃圾回收的算法与实现

中村成洋、相川光 / 丁灵 / 人民邮电出版社 / 2016-7-1 / 99.00元

★ Ruby之父Matz作推荐语:上古传承的魔法,彻底揭开垃圾回收的秘密! ★ 日本天才程序员兼Lisp黑客竹内郁雄审校 本书前半介绍基本GC算法,包括标记-清除GC、引用计数、复制算法的GC、串行GC的算法、并发GC的算法等。后半介绍V8、Rubinius、Dalvik、CPython等几种具体GC的实现。本书适合各领域程序员阅读。一起来看看 《垃圾回收的算法与实现》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具