关注 spark技术分享,
撸spark源码 玩spark最佳实践

JobScheduler, Job, JobSet 详解

阅读本文前,请一定先阅读 [Spark Streaming 实现思路与模块概述](0.1 Spark Streaming 实现思路与模块概述.md) 一文,其中概述了 Spark Streaming 的 4 大模块的基本作用,有了全局概念后再看本文对 模块 2:Job 动态生成 细节的解释。

引言

前面在 [Spark Streaming 实现思路与模块概述](0.1 Spark Streaming 实现思路与模块概述.md) 和 [DStream 生成 RDD 实例详解](1.2 DStream 生成 RDD 实例详解.md) 里我们分析了 DStream 和 DStreamGraph 具有能够实例化 RDD 和 RDD DAG 的能力,下面我们来看 Spark Streaming 是如何将其动态调度的。

在 Spark Streaming 程序的入口,我们都会定义一个 batchDuration,就是需要每隔多长时间就比照静态的 DStreamGraph来动态生成一个 RDD DAG 实例。在 Spark Streaming 里,总体负责动态作业调度的具体类是 JobScheduler,在 Spark Streaming 程序在 ssc.start() 开始运行时,将 JobScheduler 的实例给 start() 运行起来。

Spark Streaming 的 Job 总调度者 JobScheduler

JobScheduler 是 Spark Streaming 的 Job 总调度者

JobScheduler 有两个非常重要的成员:JobGenerator 和 ReceiverTrackerJobScheduler 将每个 batch 的 RDD DAG 具体生成工作委托给 JobGenerator,而将源头输入数据的记录工作委托给 ReceiverTracker

image

JobGenerator 维护了一个定时器,周期就是我们刚刚提到的 batchDuration定时为每个 batch 生成 RDD DAG 的实例。 具体的,根据我们在 [DStream 生成 RDD 实例详解](1.2 DStream 生成 RDD 实例详解.md) 中的解析,DStreamGraph.generateJobs(time) 将返回一个 Seq[Job],其中的每个 Job 是一个 ForEachDStream 实例的 generateJob(time) 返回的结果。

image

此时,JobGenerator 拿到了 Seq[Job] 后(如上图 (2) ),就将其包装成一个 JobSet(如上图 (3) ),然后就调用 JobScheduler.submitJobSet(jobSet) 来交付回 JobScheduler(如上图 (4) )。

那么 JobScheduler 收到 jobSet 后是具体如何处理的呢?我们看其实现:

这里最重要的处理逻辑是 job => jobExecutor.execute(new JobHandler(job)),也就是将每个 job 都在 jobExecutor 线程池中、用 new JobHandler 来处理。

JobHandler

先来看 JobHandler 针对 Job 的主要处理逻辑:

也就是说,JobHandler 除了做一些状态记录外,最主要的就是调用 job.run()!这里就与我们在 [DStream 生成 RDD 实例详解](1.2 DStream 生成 RDD 实例详解.md) 里分析的对应起来了: 在 ForEachDStream.generateJob(time) 时,是定义了 Job 的运行逻辑,即定义了 Job.func。而在 JobHandler 这里,是真正调用了 Job.run()、将触发 Job.func 的真正执行!

Job 运行的线程池 jobExecutor

上面 JobHandler 是解决了做什么的问题,本节 jobExecutor 是解决 Job 在哪里做。

具体的,jobExecutor 是 JobScheduler 的成员:

也就是,ThreadUtils.newDaemonFixedThreadPool() 调用将产生一个名为 "streaming-job-executor" 的线程池,所以,Job 将在这个线程池的线程里,被实际执行 func

spark.streaming.concurrentJobs 参数

这里 jobExecutor 的线程池大小,是由 spark.streaming.concurrentJobs 参数来控制的,当没有显式设置时,其取值为 1

进一步说,这里 jobExecutor 的线程池大小,就是能够并行执行的 Job 数。而回想前文讲解的 DStreamGraph.generateJobs(time) 过程,一次 batch 产生一个 Seq[Job},里面可能包含多个 Job —— 所以,确切的,有几个 output 操作,就调用几次 ForEachDStream.generatorJob(time),就产生出几个 Job

为了验证这个结果,我们做一个简单的小测试:先设置 spark.streaming.concurrentJobs = 10,然后在每个 batch 里做 2次 foreachRDD() 这样的 output 操作:

在上面的设定下,我们很容易知道,能够同时在处理的 batch 有 10 / 2 = 5 个,其余的 batch 的 Job 只能处于等待处理状态。

下面的就是刚才测试代码的运行结果,验证了我们前面的分析和计算:

image

Spark Streaming 的 JobSet, Job,与 Spark Core 的 Job, Stage, TaskSet, Task

最后,我们专门拿出一个小节,辨别一下这 Spark Streaming 的 JobSet, Job,与 Spark Core 的 Job, Stage, TaskSet, Task 这几个概念。

Spark Core 的 Job, Stage, Task 就是我们“日常”谈论 Spark 任务时所说的那些含义,而且在 Spark 的 WebUI 上有非常好的体现,比如下图就是 1 个 Job 包含 3 个 Stage;3 个 Stage 各包含 8, 2, 4 个 Task。而 TaskSet 则是 Spark Core 的内部代码里用的类,是 Task 的集合,和 Stage 是同义的。

image

而 Spark Streaming 里也有一个 Job,但此 Job 非彼 Job。Spark Streaming 里的 Job 更像是个 Java 里的 Runnable,可以 run() 一个自定义的 func 函数。而这个 func, 可以:

  • 直接调用 RDD 的 action,从而产生 1 个或多个 Spark Core 的 Job
  • 先打印一行表头;然后调用 firstTen = RDD.collect(),再打印 firstTen 的内容;最后再打印一行表尾 —— 这正是 DStream.print() 的 Job 实现
  • 也可以是任何用户定义的 code,甚至整个 Spark Streaming 执行过程都不产生任何 Spark Core 的 Job —— 如上一小节所展示的测试代码,其 Job 的 func 实现就是:Thread.sleep(Int.MaxValue),仅仅是为了让这个 Job 一直跑在 jobExecutor 线程池里,从而测试 jobExecutor 的并行度 🙂

最后,Spark Streaming 的 JobSet 就是多个 Job 的集合了。

如果对上面 5 个概念做一个层次划分的话(上一层与下一层多是一对多的关系,但不完全准确),就应该是下表的样子:

Spark CoreSpark Streaming
lv 5RDD DAGsDStreamGraph
lv 4RDD DAGJobSet
lv 3JobJob
lv 2Stage
lv 1Task

附录

赞(0) 打赏
未经允许不得转载:spark技术分享 » JobScheduler, Job, JobSet 详解
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

关注公众号:spark技术分享

联系我们联系我们

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏