Spark调度系统:如何把握分布式计算的精髓?
在上一讲,我们通过“包工头与施工工人”的例子,初步认识了Spark进程模型中的Driver和Executors、以及它们之间的交互关系。Driver负责解析用户代码、构建计算流图,然后将计算流图转化为分布式任务,并把任务分发给集群中的Executors交付运行。
不过,你可能会好奇:“对于给定的用户代码和相应的计算流图,Driver是怎么把计算图拆解为分布式任务,又是按照什么规则分发给Executors的呢?还有,Executors具体又是如何执行分布式任务的呢?”
我们之前一再强调,
而上面的这一系列问题,恰恰是我们吃透分布式计算的关键所在。因此,今天这一讲,我们就顺着这些问题,一起去深入探究Spark调度系统,进而弄清楚分布式计算的来龙去脉。
角色划分与斯巴克建筑集团
在上一讲,我们通过“包工头与施工工人”的类比、以及Word Count的示例,其实已经大致厘清了Spark分布式任务调度的核心环节与关键步骤。今天这一讲的核心任务,就是带你去深入其中的每一个环节,做到“既见森林、也见树木”。这里咱们不妨先把这些环节和涉及的组件梳理出来,从而让你在整体上有一个清晰的把握。
不难发现,表中的步骤与组件众多,要是照本宣科地去讲调度系统,先别说你可能看着看着就开始犯困了,就连我自己,也可能写着写着就睡着了。因此,咱们不妨把这些环节与组件融入到一个故事中去,让你像读小说一样,在捧腹之余弄懂Spark调度系统。
话说很久以前,美国有一家名扬海内外的建筑集团,名为“斯巴克(Spark)”。这家建筑集团规模庞大,设有一个总公司(Driver),和多个分公司(Executors)。斯巴克公司的主要服务对象是建筑设计师(开发者),建筑设计师负责提供设计图纸(用户代码、计算图),而斯巴克公司的主营业务是将图纸落地、建造起一栋栋高楼大厦。
要完成主营业务,集团公司需要招聘能够看懂图纸、并将其转化为建筑项目的架构师,因此斯巴克公司挖角了行业知名架构师“戴格”(DAGScheduler)。集团公司给戴格安排的职位是总公司的一把手,同时要求两位创始元老“塔斯克”和“拜肯德”全力配合戴格的工作。
听到这里,你肯定会问“塔斯克”和“拜肯德”是谁呢?
塔斯克(TaskScheduler)一毕业就加入了斯巴克公司,现任总公司施工经理,成功指挥完成了多个大大小小的工程项目,业绩非常突出,深得公司赏识。拜肯德(SchedulerBackend)和塔斯克在上大学的时候就是上下铺,关系好得穿一条裤子,现任总公司人力资源总监,负责与分公司协调、安排人力资源。从公司的安排来看,三位主管的分工还是比较明确的。
值得一提的是,SchedulerBackend组件的实例化,取决于开发者指定的Spark MasterURL,也就是我们使用spark-shell(或是spark-submit)时指定的–master 参数,如“–master spark://ip:host”就代表Standalone 部署模式,“–master yarn”就代表YARN 模式等等。
那么,了解了这三个主管的角色职责,我们接下来就来详细说说,他们是怎么各自完成自己的工作的。
总架戴格:DAGScheduler
回到我们的故事里,戴格在两位元老的协助下,工作开展得还算顺利,然而,冰层之下,暗流涌动,作为一名空降的领导,戴老板还需亲自“露两手”,才能赢得平级的认可与信任。
在[第2讲]介绍编程模型的时候,我们以Word Count为例,提到Spark作业的运行分为两个环节,第一个是以惰性的方式构建计算图,第二个则是通过Actions算子触发作业的从头计算:
对于图中的第二个环节,Spark在实际运行的过程中,会把它再细化为两个步骤。第一个步骤,就是以Shuffle为边界,从后向前以递归的方式,把逻辑上的计算图DAG,转化成一个又一个Stages。
我们还是以Word Count为例,Spark以take算子为起点,依次把DAG中的RDD划入到第一个Stage,直到遇到reduceByKey算子。由于reduceByKey算子会引入Shuffle,因此第一个Stage创建完毕,且只包含wordCounts这一个RDD。接下来,Spark继续向前回溯,由于未曾碰到会引入Shuffle的算子,因此它把“沿途”所有的RDD都划入了第二个Stage。
你可能会问:“Task代表的是分布式任务,不过它具体是什么呢?”要更好地认识Task,我们不妨来看看它的关键属性。
到这里,我们讲完了戴格的职责,让我们来一起简单汇总一下,戴格指代的是DAGScheduler,DAGScheduler的主要职责有三个:
-
根据用户代码构建DAG; -
以Shuffle为边界切割Stages; -
基于Stages创建TaskSets,并将TaskSets提交给TaskScheduler请求调度。
现在,戴格不辱使命,完成了“建筑图纸”到“建筑项目”的转化,接下来,他需要把这些“活儿”下派给塔斯克,由塔斯克进一步完成任务的委派。
不过,对于塔斯克来说,要想把这些“活儿”委派出去,他得先摸清楚集团内有多少“适龄劳动力”才行。要做到这一点,他必须仰仗死党:拜肯德的帮忙。
拜肯德:SchedulerBackend
作为集团公司的人力资源总监,拜肯德的核心职责,就是实时汇总并掌握全公司的人力资源状况。前面我们讲了,全公司的人力资源对应的就是Spark的计算资源。
显然,基于Executor资源画像,SchedulerBackend可以同时提供多个WorkerOffer用于分布式任务调度。WorkerOffer这个名字起得很传神,Offer的字面意思是公司给你提供的工作机会,到了Spark调度系统的上下文,它就变成了使用硬件资源的机会。
你可能会好奇,坐镇总公司的拜肯德,对于整个集团的人力资源,他是怎么做到足不出户就如数家珍的?一个篱笆三个桩,一个好汉三个帮。仅凭拜肯德一己之力,自然是力不从心,幕后功臣实际上是驻扎在分公司的一众小弟们:ExecutorBackend。
塔斯克:TaskScheduler
一把手戴格有“活儿”,三把手拜肯德出“人力”,接下来,终于轮到牵线搭桥的塔斯克出马了。作为施工经理,塔斯克的核心职责是,给定拜肯德提供的“人力”,遴选出最合适的“活儿”并派发出去。而这个遴选的过程,就是任务调度的核心所在,如下图步骤3所示:
我们刚刚说过,
回到斯巴克建筑集团的类比,就好比是某个“活儿”,并不是所有人都能干,而是只倾向于让某些人来做,因为他们更专业。比如砌墙这件事,更倾向于给工龄3年以上的瓦工来做;而吊顶,则更倾向于给经验超过5年的木工来做,诸如此类。
下图展示的是,TaskScheduler依据本地性倾向,依次进行任务调度的运行逻辑:
不难发现,从PROCESS_LOCAL、NODE_LOCAL、到RACK_LOCAL、再到ANY,Task的本地性倾向逐渐从严苛变得宽松。TaskScheduler接收到WorkerOffer之后,也正是按照这个顺序来遍历TaskSet中的Tasks,优先调度本地性倾向为PROCESS_LOCAL的Task,而NODE_LOCAL次之,RACK_LOCAL为再次,最后是ANY。
你可能会问:
好啦,到此为止,
那么小弟ExecutorBackend拿到活之后,是怎么工作的呢?我们接着往下看吧!
付诸执行:ExecutorBackend
作为分公司的人力资源主管,ExecutorBackend拿到“活儿”之后,随即把活儿派发给分公司的建筑工人。这些工人,就是Executors线程池中一个又一个的CPU线程,每个线程负责处理一个Task。
每当Task处理完毕,这些线程便会通过ExecutorBackend,向Driver端的SchedulerBackend发送StatusUpdate事件,告知Task执行状态。接下来,TaskScheduler与SchedulerBackend通过接力的方式,最终把状态汇报给DAGScheduler,如图中步骤7、8、9所示:
对于同一个TaskSet当中的Tasks来说,当它们分别完成了任务调度与任务执行这两个环节时,也就是上图中步骤1到步骤9的计算过程,Spark调度系统就完成了DAG中某一个Stage的任务调度。
路遥知马力,在一起合作了一个又一个建筑项目之后,空降老大戴格终于赢得了元老塔斯克和拜肯德的信任与认可,坐稳了斯巴克建筑集团的头把交椅。来日可期,戴格的前景一片光明。
重点回顾
今天这一讲,我们用斯巴克建筑集团的故事,介绍了Spark调度系统的工作原理。对于调度系统的工作流程,你需要掌握表格中的5个关键环节:
具体说来,任务调度分为如下5个步骤:
-
DAGScheduler以Shuffle为边界,将开发者设计的计算图DAG拆分为多个执行阶段Stages,然后为每个Stage创建任务集TaskSet。- -
SchedulerBackend通过与Executors中的ExecutorBackend的交互来实时地获取集群中可用的计算资源,并将这些信息记录到ExecutorDataMap数据结构。- -
与此同时,SchedulerBackend根据ExecutorDataMap中可用资源创建WorkerOffer,以WorkerOffer为粒度提供计算资源。- -
对于给定WorkerOffer,TaskScheduler结合TaskSet中任务的本地性倾向,按照PROCESS_LOCAL、NODE_LOCAL、RACK_LOCAL和ANY的顺序,依次对TaskSet中的任务进行遍历,优先调度本地性倾向要求苛刻的Task。- -
被选中的Task由TaskScheduler传递给SchedulerBackend,再由SchedulerBackend分发到Executors中的ExecutorBackend。Executors接收到Task之后,即调用本地线程池来执行分布式任务。
今天的内容就是这些,调度系统是分布式计算系统的核心,掌握了Spark任务调度的来龙去脉,你也就把握住了Spark分布式计算引擎的精髓,这会为你开发出高性能的Spark分布式应用打下坚实基础。
共有 0 条评论