第26课: Spark Runtime(Driver、Masster、Worker、Executor)内幕解密

来源:互联网 发布:守望先锋英雄数据库 编辑:程序博客网 时间:2024/05/29 06:45

第26课:   Spark Runtime(Driver、Masster、Worker、Executor)内幕解密

坚持梦想,永不放弃!


本节从Spark Runtime全局的角度看Spark具体怎么工作的,从一个作业的视角通过Driver、Masster、Worker、Executor等角色来透视Spark的Runtime。本节内容包括:1、再论Spark集群部署;2、Job提交解密;3、Job的生成和接受;4、Task的运行;5、再论Shuffle。

(1)从Spark Runtime的角度来讲,包括五大核心对象:Master、Worker、Executor、Driver、CoarseGrainedExecutorBackend;

(2)Spark在做分布式集群系统设计的时候:最大化功能独立、模块化封装具体独立的对象、强内聚松耦合。

(3)当Driver中的SparkContext初始化的时候会提交程序给Master,Master如果接受该程序在Spark中运行的话,就会为当前的程序分配AppID,同时会分配具体的计算资源,需要特别注意的是,Master是根据当前提交程序的配置信息来给集群中的Worker发指令分配具体的计算资源,但是,Master发出指令后并不关心具体的资源是否已经分配,转来说Master是发指令后就记录了分配的资源,以后客户端再次提交其它的程序的话就不能使用该资源了。其弊端是可能会导致其它要提交的程序无法分配到本来应该可以分配到的计算资源;最终的优势在Spark分布式系统功能若耦合的基础上最快的运行系统(否则如果Master要等到资源最终分配成功后才通知Driver的话,就会造成Driver阻塞,不能够最大化并行计算资源的使用率)。需要补充说明的是:Spark在默认情况下由于集群中一般都只有一个Application在运行,所有Master分配资源策略的弊端就没有那么明显了。

 

Job提交过程源码解密中一个非常重要的技巧通过在spark-shell中运行一个Job来了解Job提交的过程,然后在用源码验证这个过程;我们可以在spark-shell中运行中运行一个程序,从控制台观察日志。

1.         ,sc.textFile("/library/dataforSortedShufffle").flatMap(_.split("")).map(word => (word,1).reduceByKey(_+_)saveAsTextFile("/library/dataoutput2")  

这里我们编写WordCountJobRuntime.scala代码,从IDEA中观察日志。读入的数据源文件内容如下:

1.         Hello Spark Hello Scala

2.         Hello Hadoop

3.         Hello Flink

4.         Spark is Awesome

WordCountJobRuntime.scala代码如下:

5.          package com.dt.spark.sparksql

6.          

7.         import org.apache.log4j.{Level, Logger}

8.         import org.apache.spark.{SparkConf, SparkContext}

9.          

10.      /**

11.        * 使用Scala开发本地测试的Spark WordCount程序

12.        * @author DT大数据梦工厂

13.        * 新浪微博:http://weibo.com/ilovepains/

14.        */

15.      object WordCountJobRuntime {

16.        def main(args: Array[String]){

17.          Logger.getLogger("org").setLevel(Level.ALL)

18.          /**

19.            * 第1步:创建Spark的配置对象SparkConf,设置Spark程序的运行时的配置信息, 例如说通过setMaster来设置程序要链接的Spark集群的Master的URL,如果设置

20.      为local,则代表Spark程序在本地运行,特别适合于机器配置条件非常差(例如

21.      只有1G的内存)的初学者       *

22.            */

23.          val conf = new SparkConf() //创建SparkConf对象

24.          conf.setAppName("Wow,WordCountJobRuntime!")//设置应用程序的名称,在程序运行的监控界面可以看到名称

25.          conf.setMaster("local") //此时,程序在本地运行,不需要安装Spark集群

26.       

27.          /**

28.            * 第2步:创建SparkContext对象

29.            * SparkContext是Spark程序所有功能的唯一入口,无论是采用Scala、Java、Python、R等都必须有一个SparkContext

30.            * SparkContext核心作用:初始化Spark应用程序运行所需要的核心组件,包括DAGScheduler、TaskScheduler、SchedulerBackend   同时还会负责Spark程序往Master注册程序等

31.            * SparkContext是整个Spark应用程序中最为至关重要的一个对象

32.            */

33.          val sc = newSparkContext(conf) //创建SparkContext对象,通过传入SparkConf实例来定制Spark运行的具体参数和配置信息

34.       

35.          /**

36.            * 第3步:根据具体的数据来源(HDFS、HBase、Local FS、DB、S3等)通过SparkContext来创建RDD

37.            * RDD的创建基本有三种方式:根据外部的数据来源(例如HDFS)、根据Scala集合、由其它的RDD操作

38.            * 数据会被RDD划分成为一系列的Partitions,分配到每个Partition的数据属于一个Task的处理范畴

39.            */

40.           val lines =sc.textFile("data/wordcount/helloSpark.txt") 

41.          /**

42.            * 第4步:对初始的RDD进行Transformation级别的处理,例如map、filter等高阶函数等的编程,来进行具体的数据计算

43.            *    第4.1步:讲每一行的字符串拆分成单个的单词

44.            */

45.       

46.          val words = lines.flatMap {line => line.split(" ")} //对每一行的字符串进行单词拆分并把所有行的拆分结果通过flat合并成为一个大的单词集合

47.       

48.          /**

49.            * 第4步:对初始的RDD进行Transformation级别的处理,例如map、filter等高阶函数等的编程,来进行具体的数据计算

50.            *    第4.2步:在单词拆分的基础上对每个单词实例计数为1,也就是word => (word, 1)

51.            */

52.          val pairs = words.map { word=> (word, 1) }

53.       

54.          /**

55.            * 第4步:对初始的RDD进行Transformation级别的处理,例如map、filter等高阶函数等的编程,来进行具体的数据计算

56.            *    第4.3步:在每个单词实例计数为1基础之上统计每个单词在文件中出现的总次数

57.            */

58.          val wordCountsOdered =pairs.reduceByKey(_+_).saveAsTextFile("data/wordcount/wordCountResult.log")

59.       

60.          while(true){

61.            

62.          }

63.          sc.stop()

64.       

65.        }

66.      }

 

在IDEA中运行,WordCountJobRuntime的运行结果保存在data/wordcount/wordCountResult.log 目录的part-00000中:

1.          (Awesome,1)

2.         (Flink,1)

3.         (Spark,2)

4.         (is,1)

5.         (Hello,4)

6.         (Scala,1)

7.         (Hadoop,1)

8.          

 

我们在IDEA的控制台页面中观察WordCountJobRuntime.scala运行日志,其中 MemoryStore是从Storge内存角度来看的,Storge是磁盘管理和内存管理。这里Spark读取了Hadoop的HDFS,因此使用了Hadoop的内容,如FileInputFormat,日志中显示FileInputFormat: Total input paths to process : 1说明有1个文件要处理:

1.          Using Spark's default log4jprofile: org/apache/spark/log4j-defaults.properties

2.         17/05/24 05:48:20 INFO SparkContext: Running Spark version 2.1.0

3.         ……

4.         17/05/24 05:48:24 DEBUG DiskBlockManager: Adding shutdown hook

5.         17/05/24 05:48:24 DEBUG ShutdownHookManager: Adding shutdown hook

6.         17/05/24 05:48:24 INFO MemoryStore: MemoryStore started with capacity637.2 MB

7.         17/05/24 05:48:24 INFO SparkEnv: Registering OutputCommitCoordinator

8.         ……

9.         17/05/24 05:48:27 DEBUG HadoopRDD: Creating new JobConf and caching itfor later re-use

10.      17/05/24 05:48:27 DEBUG FileInputFormat: Time taken to get FileStatuses:28

11.      17/05/24 05:48:27 INFO FileInputFormat: Total input paths to process : 1

12.      17/05/24 05:48:27 DEBUG FileInputFormat: Total # of splits generated bygetSplits: 1, TimeTaken: 48

13.      ……

 

在Spark中所有的Action都会触发一个至少一个Job,在WordCountJobRuntime.scala代码中是通过saveAsTextFile来触发Job的;日志中查看SparkContext: Starting job: saveAsTextFile 触发saveAsTextFile。紧接着交给DAGScheduler,日志中显示DAGScheduler: Registering RDD,因为这里有2个Stage,从具体计算的角度,前面Stage计算的时候保留输出。然后是DAGScheduler获得了job的ID(job 0)。

1.          17/05/24 05:48:28 INFOSparkContext: Starting job: saveAsTextFile at WordCountJobRuntime.scala:61

2.         17/05/24 05:48:28 DEBUG SortShuffleManager: Can't use serialized shufflefor shuffle 0 because an aggregator is defined

3.         17/05/24 05:48:28 INFO DAGScheduler: Registering RDD 3 (map atWordCountJobRuntime.scala:55)

4.         17/05/24 05:48:28 INFO DAGScheduler: Got job 0 (saveAsTextFile atWordCountJobRuntime.scala:61) with 1 output partitions

5.         17/05/24 05:48:28 INFO DAGScheduler: Final stage: ResultStage 1(saveAsTextFile at WordCountJobRuntime.scala:61)

6.         17/05/24 05:48:28 INFO DAGScheduler: Parents of final stage:List(ShuffleMapStage 0)

7.         17/05/24 05:48:28 INFO DAGScheduler: Missing parents: List(ShuffleMapStage0)

8.         17/05/24 05:48:28 DEBUG DAGScheduler: submitStage(ResultStage 1)

9.         17/05/24 05:48:28 DEBUG DAGScheduler: missing: List(ShuffleMapStage 0)

10.      17/05/24 05:48:28 DEBUG DAGScheduler: submitStage(ShuffleMapStage 0)

11.      17/05/24 05:48:28 DEBUG DAGScheduler: missing: List()

12.      17/05/24 05:48:28 INFO DAGScheduler: Submitting ShuffleMapStage 0(MapPartitionsRDD[3] at map at WordCountJobRuntime.scala:55), which has nomissing parents

13.      17/05/24 05:48:28 DEBUG DAGScheduler: submitMissingTasks(ShuffleMapStage0)

14.      17/05/24 05:48:28 TRACE BlockInfoManager: Task -1024 trying to putbroadcast_1

 

SparkContext在实例化的时候会构造SparkDeploySchedulerBackend、DAGScheduler、TaskSchedulerImpl、MapOutputTrackerMaster等对象:

l  其中SparkDeploySchedulerBackend负责集群计算资源的管理和调度,这是从作业的角度来考虑,注册给Master的时候,Master给我们分配资源,资源从Executor本身转过来向SparkDeploySchedulerBackend注册,这是从作业调度的角度来考虑的,不是从整个集群来考虑,整个集群是Master来管理计算资源的。

l  DAGScheduler负责高层调度(例如Job中Stage的划分、数据本地性等内容),

l  TaskSchedulerImple负责具体Stage内部的底层调度(例如具体每个Task的调度、Task的容错等)

l  MapOutputTrackerMaster负责Shuffle中数据输出和读取的管理。Shuffle的时候将数据写到本地,下一个Stage要使用上一个Stage的数据,因此写数据的时候要告诉Driver中的MapOutputTrackerMaster具体写到哪里,下一个Stage读取数据的时候也要访问Driver的MapOutputTrackerMaster获取数据的具体位置。

MapOutputTrackerMaster的源码如下:

1.          private[spark] classMapOutputTrackerMaster(conf: SparkConf,

2.             broadcastManager: BroadcastManager,isLocal: Boolean)

3.           extends MapOutputTracker(conf) {

 

DAGScheduler是面向Stage调度的高层调度实现。它为每一个job计算DAG,跟踪RDDS及Stage输出结果并进行物化,并找到一个最小的计划去运行job,然后提交stages中TaskSets到底层调度器TaskScheduler提交集群运行,TaskSet包含完全独立的任务,基于集群上已存在的数据运行(例如从上一个Stage输出的文件),如果这个数据不可用,获取数据可能会失败。

Spark stages根据RDD图中shuffle的边界来创建,如果RDD的操作是窄依赖,例如map()和filter(),在每一个Stages中将一系列tasks组合成流水线执行。但是如果是宽依赖,shuffle依赖需要多个Stages(上一个Stage进行map输出写入文件,下一个Stage读取数据文件),每一个Stage依赖于其它的Stage,其中进行多个算子操作。算子操作在各种类型的RDDS(例如MappedRDD,FilteredRDD)的RDD.compute()中实际执行。




在DAG阶段,DAGScheduler根据当前缓存状态决定每个任务运行的位置,并将任务传递给底层的任务调度器TaskScheduler。此外,它处理shuffle输出文件丢失的故障,在这种情况下以前的Stage可能需要重新提交。Stage中不引起shuffle文件丢失的故障由任务调度器TaskScheduler处理,在取消整个stage前将重试几次任务。

当浏览这个代码时,有几个关键概念:

l  Jobs作业( 表现为[[ActiveJob]])作为顶级工作项提交给调度程序。当用户调用一个action,例如count()算子,job将通过submitJob进行提交。每个作业可能需要执行多个stages来构建中间数据。

l  Stages ([[Stage]])是一组任务的集合,在相同的RDD分区上,每个任务计算相同的功能,计算jobs的中间结果。Stage根据Shuffle划分边界,我们必须等待前一阶段Stage完成输出。有两种类型的Stage:[[ResultStage]]是执行action的最后一个stage ,[[ShuffleMapStage]]在shuffle Stages通过map写入输出文件中。如果jobs重用相同的RDDs ,Stages可以跨越多个jobs共享。

l  Tasks任务是单独的工作单位,每个发送到一台机器。

l  缓存跟踪:DAGScheduler记录哪些RDDS被缓存,避免重复计算。以及记录shuffle map stages已经生成的输出文件,避免在map端重新计算。

l  数据本地化:DAGScheduler基于RDDS的数据本地性、缓存位置、或shuffle数据在Stage中运行每一个任务的Task。

l  清理:当依赖于它们的运行作业完成时,所有数据结构将被清除,防止在长期运行的应用程序中内存泄漏。

l  为了从故障中恢复,同一个stage可能需要运行多次,这被称为重试“attempts”。 如在上一个Stage中的输出文件丢失,TaskScheduler中将报告任务失败,DAGScheduler通过检测CompletionEvent与FetchFailed或ExecutorLost事件重新提交丢失的Stage。DAGScheduler将等待看是否有其他节点或任务失败,然后在丢失计算任务的阶段Stage中重新提交TaskSets 。在这个过程中,可能需创建之前被清理的Stage。 旧Stage的任务仍然可以运行,但必须在正确的Stage中接收事件并进行操作。

当做改变或者回顾时候需要看的清单:

l  job运行结束时,所有的数据结构将被清理,避免长期运行程序中状态的无限累加。

l  添加一个新的数据结构时,在新结构中更新`DAGSchedulerSuite.assertDataStructuresEmpty`,包括新结构。将有助于捕获内存泄漏。

 

我们看一下DAGScheduler.scala的源代码:

1.          private[spark]

2.         class DAGScheduler(

3.             private[scheduler] val sc:SparkContext,

4.             private[scheduler] valtaskScheduler: TaskScheduler,

5.             listenerBus: LiveListenerBus,

6.             mapOutputTracker:MapOutputTrackerMaster,

7.             blockManagerMaster:BlockManagerMaster,

8.             env: SparkEnv,

9.             clock: Clock = newSystemClock())

10.        extends Logging {

 

回到我们的运行日志,SparkContext在实例化的时候会构造SparkDeploySchedulerBackend、DAGScheduler、TaskSchedulerImpl、MapOutputTrackerMaster等四大核心对象,DAGScheduler获得job ID,日志中显示DAGScheduler:Final stage: ResultStage 1 , Final stage是ResultStage;Parents offinal stage是ShuffleMapStage,DAGScheduler是面向Stage的。日志中显示2个Stage:Stage 1是Final stage,Stage 0是ShuffleMapStage。

接下来序号改变,运行时最左侧从0开始,日志中显示 DAGScheduler: missing: List(ShuffleMapStage 0),父Stage是ShuffleMapStage,DAGScheduler调度时必须先计算父Stage,因此首先提交的是ShuffleMapStage 0 ,这里RDD是MapPartitionsRDD,只有Stage中的最后一个算子是真正有效的,Stage 0中的最后一个操作是map,因此生成的MapPartitionsRDD。Stage 0无父Stage因此进行提交,提交时进行广播等内容,然后提交作业。

 

1.         ……

2.         17/05/24 05:48:28 INFO DAGScheduler: Final stage: ResultStage 1(saveAsTextFile at WordCountJobRuntime.scala:61)

3.         17/05/24 05:48:28 INFO DAGScheduler: Parents of final stage:List(ShuffleMapStage 0)

4.         17/05/24 05:48:28 INFO DAGScheduler: Missing parents:List(ShuffleMapStage 0)

5.         17/05/24 05:48:28 DEBUG DAGScheduler: submitStage(ResultStage 1)

6.         17/05/24 05:48:28 DEBUG DAGScheduler: missing: List(ShuffleMapStage 0)

7.         17/05/24 05:48:28 DEBUG DAGScheduler: submitStage(ShuffleMapStage 0)

8.         17/05/24 05:48:28 DEBUG DAGScheduler: missing: List()

9.         17/05/24 05:48:28 INFO DAGScheduler: Submitting ShuffleMapStage 0 (MapPartitionsRDD[3]at map at WordCountJobRuntime.scala:55), which has no missing parents

10.      17/05/24 05:48:28 DEBUG DAGScheduler: submitMissingTasks(ShuffleMapStage0)

11.      17/05/24 05:48:28 TRACE BlockInfoManager: Task -1024 trying to putbroadcast_1

12.      ……

 

我们从webUI的角度看一下,webUI中显示生成2个Stage:Stage0、Stage1:

图 3- 1 Stage划分

 

日志中显示DAGScheduler:Submitting 1 missing tasks from ShuffleMapStage 0,DAGScheduler提交作业,显示提交1个需计算的任务,ShuffleMapStage在本地运行是1个并行度,交给TaskSchedulerImpl运行。这里是1个并行度,提交底层的调度器TaskScheduler,TaskScheduler收到任务以后,就发布任务到集群中运行,由TaskSetManager进行管理:日志中显示TaskSetManager:Starting task 0.0 in stage 0.0 (TID 0, localhost, executor driver, partition 0,PROCESS_LOCAL, 6012 bytes) ,显示具体运行的位置。显示具体运行的位置,及worker运行了哪些任务。这里在本地只运行了1个任务。

 

1.          17/05/24 05:48:28 INFODAGScheduler: Submitting ShuffleMapStage 0 (MapPartitionsRDD[3] at map atWordCountJobRuntime.scala:55), which has no missing parents

2.         17/05/24 05:48:28 DEBUG DAGScheduler: submitMissingTasks(ShuffleMapStage0)

3.         ......

4.         17/05/24 05:48:28 INFO DAGScheduler: Submitting 1 missing tasks fromShuffleMapStage 0 (MapPartitionsRDD[3] at map at WordCountJobRuntime.scala:55)

5.         17/05/24 05:48:28 DEBUG DAGScheduler: New pending partitions: Set(0)

6.         17/05/24 05:48:28 INFO TaskSchedulerImpl: Adding task set 0.0 with 1tasks

7.         17/05/24 05:48:28 DEBUG TaskSetManager: Epoch for TaskSet 0.0: 0

8.         17/05/24 05:48:28 DEBUG TaskSetManager: Valid locality levels forTaskSet 0.0: NO_PREF, ANY

9.         17/05/24 05:48:28 DEBUG TaskSchedulerImpl: parentName: , name:TaskSet_0.0, runningTasks: 0

10.      17/05/24 05:48:28 DEBUG TaskSetManager: Valid locality levels forTaskSet 0.0: NO_PREF, ANY

11.      17/05/24 05:48:28 DEBUG SecurityManager: user=null aclsEnabled=falseviewAcls=dell viewAclsGroups=

12.      17/05/24 05:48:28 INFO TaskSetManager: Starting task 0.0 in stage 0.0(TID 0, localhost, executor driver, partition 0, PROCESS_LOCAL, 6012 bytes)

13.      17/05/24 05:48:28 INFO Executor: Running task 0.0 in stage 0.0 (TID 0)

14.      17/05/24 05:48:28 DEBUG Executor: Task 0's epoch is 0

 

然后是完成作业,日志中显示TaskSetManager: Finishedtask 0.0 in stage 0.0 (TID 0) in 327 ms on localhost (executor driver),在本地机器上完成作业。当Stage的1个任务完成以后,ShuffleMapStage就已完成。Task任务运行完以后向DAGScheduler汇报,DAGScheduler看曾经提交了几个Task,计算Task的数量如果等于Task的总数量,那Stage也就完成了。这个Stage完成以后,那下一个Stage开始运行。

1.         ......

2.         17/05/24 05:48:29 INFO Executor: Finished task 0.0 in stage 0.0 (TID 0).1744 bytes result sent to driver

3.         17/05/24 05:48:29 DEBUG TaskSchedulerImpl: parentName: , name:TaskSet_0.0, runningTasks: 0

4.         17/05/24 05:48:29 DEBUG TaskSetManager: No tasks for locality levelNO_PREF, so moving to locality level ANY

5.         17/05/24 05:48:29 INFO TaskSetManager: Finished task 0.0 in stage 0.0(TID 0) in 327 ms on localhost (executor driver) (1/1)

6.         17/05/24 05:48:29 INFO TaskSchedulerImpl: Removed TaskSet 0.0, whosetasks have all completed, from pool

7.         17/05/24 05:48:29 DEBUG DAGScheduler: ShuffleMapTask finished on driver

8.         17/05/24 05:48:29 INFO DAGScheduler: ShuffleMapStage 0 (map atWordCountJobRuntime.scala:55) finished in 0.358 s

 

ShuffleMapStage完成以后,将运行下一个Stage。日志中显示DAGScheduler: looking for newlyrunnable stages,我们这里一共2个Stage,ShuffleMapStage运行完成,那只有一个ResultStage将运行。DAGScheduler又提交最后一个Stage的1个任务,默认并行度是继承的。同样的,发布任务给Executor进行计算。

1.         ……

2.         17/05/24 05:48:29 INFO DAGScheduler: looking for newly runnable stages

3.         17/05/24 05:48:29 INFO DAGScheduler: running: Set()

4.         17/05/24 05:48:29 INFO DAGScheduler: waiting: Set(ResultStage 1)

5.         17/05/24 05:48:29 INFO DAGScheduler: failed: Set()

6.          17/05/24 05:48:29 DEBUGMapOutputTrackerMaster: Increasing epoch to 1

7.         17/05/24 05:48:29 TRACE DAGScheduler: Checking if any dependencies ofShuffleMapStage 0 are now runnable

8.         17/05/24 05:48:29 TRACE DAGScheduler: running: Set()

9.         17/05/24 05:48:29 TRACE DAGScheduler: waiting: Set(ResultStage 1)

10.      17/05/24 05:48:29 TRACE DAGScheduler: failed: Set()

11.      17/05/24 05:48:29 DEBUG DAGScheduler: submitStage(ResultStage 1)

12.      17/05/24 05:48:29 DEBUG DAGScheduler: missing: List()

13.      17/05/24 05:48:29 INFO DAGScheduler: Submitting ResultStage 1(MapPartitionsRDD[5] at saveAsTextFile at WordCountJobRuntime.scala:61), whichhas no missing parents

14.      17/05/24 05:48:29 DEBUG DAGScheduler: submitMissingTasks(ResultStage 1)

15.      ……

16.      17/05/24 05:48:29 INFO DAGScheduler: Submitting 1 missing tasks fromResultStage 1 (MapPartitionsRDD[5] at saveAsTextFile atWordCountJobRuntime.scala:61)

17.      17/05/24 05:48:29 DEBUG DAGScheduler: New pending partitions: Set(0)

18.      17/05/24 05:48:29 INFO TaskSchedulerImpl: Adding task set 1.0 with 1tasks

19.      ….

 

Task任务运行完以后向DAGScheduler汇报,DAGScheduler计算曾经提交了几个Task,如果Task的数量如果等于Task的总数量,ResultStage也运行完成。然后进行相关的清理工作,2个Stage(ShuffleMapStage、ResultStage)完成,job也就完成。

1.         ……

2.         17/05/24 05:48:29 INFO TaskSetManager: Finished task 0.0 in stage 1.0(TID 1) in 409 ms on localhost (executor driver) (1/1)

3.         17/05/24 05:48:29 INFO TaskSchedulerImpl: Removed TaskSet 1.0, whosetasks have all completed, from pool

4.         17/05/24 05:48:29 INFO DAGScheduler: ResultStage 1 (saveAsTextFile atWordCountJobRuntime.scala:61) finished in 0.410 s

5.         17/05/24 05:48:29 DEBUG DAGScheduler: After removal of stage 1,remaining stages = 1

6.         17/05/24 05:48:29 DEBUG DAGScheduler: After removal of stage 0,remaining stages = 0

7.         17/05/24 05:48:29 INFO DAGScheduler: Job 0 finished: saveAsTextFile atWordCountJobRuntime.scala:61, took 1.345921 s

8.         ……

 

我们看一下WebUI,ShuffleMapStage中的任务交给Executor工作,图中显示了任务的相关信息,如Shuffle的输出等,第一个Stage肯定生成Shuffle的输出,可以看一下最右侧的ShuffleWrite Size/Records。图中InputSize/Records是从Hdfs中读入的文件数据。

图 3- 2 ShuffleMapStage运行

 

接下来我们看一下第二个ResultStage,第二个Stage同样显示Excutor的信息,最右侧显示ShuffleRead Size/Records。如果在分布式集群运行,需从远程读取数据,例如原来是4个Executor计算,在第二个Stage中是2个Executor计算,因此一部分数据是本地的,一部分是远程的,或从远程节点拉取数据。ResultStage最后要产生输出,输出到文件保存。

图 3- 3ResultStage运行

 

Task的运行解密:

         1,Task是运行在Executor中,而Executor又是位于CoarseGrainedExecutorBackend中的,且CoarseGrainedExecutorBackend和Executor是一一对应的; 计算运行于Executor,而Executor位于CoarseGrainedExecutorBackend中,CoarseGrainedExecutorBackend是进程。发任务消息也是在CoarseGrainedExecutorBackend。

2,当CoarseGrainedExecutorBackend接收到TaskSetManager发过来的LaunchTask消息后会反序列化TaskDescription,然后使用CoarseGrainedExecutorBackend中唯一的Executor来执行任务;

我们看一下CoarseGrainedExecutorBackend.scala的LaunchTask源代码,CoarseGrainedExecutorBackend收到Driver发送的LaunchTask任务消息:

1.         case  LaunchTask(data) =>

2.               if (executor == null) {

3.                 exitExecutor(1, "Received LaunchTaskcommand but executor was null")

4.               } else {

5.                 val taskDesc =ser.deserialize[TaskDescription](data.value)

6.                 logInfo("Got assignedtask " + taskDesc.taskId)

7.                 executor.launchTask(this,taskId = taskDesc.taskId, attemptNumber = taskDesc.attemptNumber,

8.                   taskDesc.name,taskDesc.serializedTask)

9.               }

 

其中LaunchTask是case class。LaunchTask是case class,而不是case object,是因为每个消息是一个消息实例,每个消息状态不一样,而case object是唯一的。因此使用caseclass。

1.              //Driver to executors

2.           case class LaunchTask(data:SerializableBuffer) extends CoarseGrainedClusterMessage

 

我们看一下Executor.scala的源代码:

1.         ……

2.            // Maintains the list of running tasks.

3.           private val runningTasks = newConcurrentHashMap[Long, TaskRunner]

4.         ……

5.           def  launchTask(

6.               context: ExecutorBackend,

7.               taskId: Long,

8.               attemptNumber: Int,

9.               taskName: String,

10.            serializedTask: ByteBuffer): Unit = {

11.          val tr = new TaskRunner(context, taskId =taskId, attemptNumber = attemptNumber, taskName,

12.            serializedTask)

13.          runningTasks.put(taskId, tr)

14.          threadPool.execute(tr)

15.        }

16.      ……

17.       

18.       class TaskRunner(

19.            execBackend:ExecutorBackend,

20.            val taskId: Long,

21.            val attemptNumber: Int,

22.            taskName: String,

23.            serializedTask: ByteBuffer)

24.          extends Runnable {

25.      …….

 

在Executor.scala中点击launchTask,运行的任务使用了ConcurrentHashMap数据结构,运行launchTask的时候构建了一个TaskRunner,TaskRunner是一个Runnable,而Runnable是java中的接口,scala可以直接调用java的代码,run方法中包括任务的反序列化等内容。通过Runnable封装我们的任务,然后放入到runningTasks中,在threadPool中执行任务。threadPool是我们的一个newDaemonCachedThreadPool。任务交给Executor的线程池中的线程去执行,执行的时候下载资源,数据等内容。

1.          // Start worker thread pool

2.           private val threadPool =ThreadUtils.newDaemonCachedThreadPool("Executor task launch worker")

 

 


阅读全文
0 0
原创粉丝点击