01-240528-Spark笔记
1. Spark简单演示(单词计数)
2. Spark01_WordCount
package com.atguigu.bigdata.spark.core.wc import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} object Spark01_WordCount { def main(args: Array[String]): Unit = { // Application // Spark框架 // TODO 建立和Spark框架的连接 // JDBC : Connection val sparConf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(sparConf) // TODO 执行业务操作 // 1. 读取文件,获取一行一行的数据 // hello world val lines: RDD[String] = sc.textFile("datas") // 2. 将一行数据进行拆分,形成一个一个的单词(分词) // 扁平化:将整体拆分成个体的操作 // "hello world" => hello, world, hello, world val words: RDD[String] = lines.flatMap(_.split(" ")) // 3. 将数据根据单词进行分组,便于统计 // (hello, hello, hello), (world, world) val wordGroup: RDD[(String, Iterable[String])] = words.groupBy(word=>word) // 4. 对分组后的数据进行转换 // (hello, hello, hello), (world, world) // (hello, 3), (world, 2) val wordToCount = wordGroup.map { case ( word, list ) => { (word, list.size) } } // 5. 将转换结果采集到控制台打印出来 val array: Array[(String, Int)] = wordToCount.collect() array.foreach(println) // TODO 关闭连接 sc.stop() } }
这段Scala代码是一个使用Apache Spark进行词频统计(Word Count)的示例。让我们逐步分析代码的每个部分:
包声明和导入:
package com.atguigu.bigdata.spark.core.wc
:声明了Scala代码的包名。import org.apache.spark.rdd.RDD
:导入了Spark的RDD类。import org.apache.spark.{SparkConf, SparkContext}
:导入了Spark的配置类和上下文类。
对象定义:
object Spark01_WordCount
:定义了一个名为Spark01_WordCount
的单例对象,这是执行Word Count的入口点。
main方法:
def main(args: Array[String]): Unit = {...}
:定义了程序的入口点,即main方法。
Spark配置和上下文创建:
val sparConf = new SparkConf().setMaster("local").setAppName("WordCount")
:创建了一个Spark配置对象,设置了运行模式为本地模式(local
),并指定了应用程序的名称为WordCount
。val sc = new SparkContext(sparConf)
:使用配置对象创建了一个Spark上下文(SparkContext
),这是与Spark集群交互的主要接口。
业务逻辑:
val lines: RDD[String] = sc.textFile("datas")
:从名为datas
的文件中读取文本数据,创建一个行的RDD。val words: RDD[String] = lines.flatMap(_.split(" "))
:将每行文本拆分成单词,创建一个单词的RDD。val wordGroup: RDD[(String, Iterable[String])] = words.groupBy(word=>word)
:将单词RDD按照单词本身进行分组,得到一个元组的RDD,其中每个元组包含一个单词和对应单词的迭代器。val wordToCount = wordGroup.map {...}
:将分组后的RDD转换成一个新RDD,其中包含单词和每个单词出现的次数。val array: Array[(String, Int)] = wordToCount.collect()
:将最终的RDD收集到驱动程序中,并存储在一个数组中。array.foreach(println)
:遍历数组并打印每个元素,即每个单词及其出现的次数。
关闭Spark上下文:
sc.stop()
:在所有操作完成后,关闭Spark上下文。 整个程序的结构是典型的Spark应用程序结构,包括配置、初始化、数据处理和关闭资源。这段代码演示了如何使用Spark进行基本的词频统计操作。
3. Spark02_WordCount2
package com.atguigu.bigdata.spark.core.wc import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} object Spark02_WordCount1 { def main(args: Array[String]): Unit = { // Application // Spark框架 // TODO 建立和Spark框架的连接 // JDBC : Connection val sparConf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(sparConf) // TODO 执行业务操作 // 1. 读取文件,获取一行一行的数据 // hello world val lines: RDD[String] = sc.textFile("datas") // 2. 将一行数据进行拆分,形成一个一个的单词(分词) // 扁平化:将整体拆分成个体的操作 // "hello world" => hello, world, hello, world val words: RDD[String] = lines.flatMap(_.split(" ")) // 3. 将单词进行结构的转换,方便统计 // word => (word, 1) val wordToOne = words.map(word=>(word,1)) // 4. 将转换后的数据进行分组聚合 // 相同key的value进行聚合操作 // (word, 1) => (word, sum) val wordToSum: RDD[(String, Int)] = wordToOne.reduceByKey(_+_) // 5. 将转换结果采集到控制台打印出来 val array: Array[(String, Int)] = wordToSum.collect() array.foreach(println) // TODO 关闭连接 sc.stop() } }
这段Scala代码是使用Apache Spark进行词频统计(Word Count)的另一个示例。它与之前的代码示例非常相似,但是在这个版本中,使用了Spark的reduceByKey
操作来简化单词计数的过程。让我们逐步分析代码的每个部分:
包声明和导入:
与上一个示例相同,这些行声明了代码的包名并导入了必要的Spark类。
对象定义:
object Spark02_WordCount1
:定义了一个名为Spark02_WordCount1
的单例对象作为程序的入口点。
main方法:
def main(args: Array[String]): Unit = {...}
:定义了程序的入口点,即main方法。
Spark配置和上下文创建:
与上一个示例相同,这些行创建了Spark配置对象和Spark上下文。
业务逻辑:
val lines: RDD[String] = sc.textFile("datas")
:从名为datas
的文件中读取文本数据,创建一个行的RDD。val words: RDD[String] = lines.flatMap(_.split(" "))
:将每行文本拆分成单词,创建一个单词的RDD。val wordToOne = words.map(word=>(word,1))
:将单词RDD转换为元组RDD,每个元组包含一个单词和数字1,这样就可以对每个单词进行计数。val wordToSum: RDD[(String, Int)] = wordToOne.reduceByKey(_+_)
:使用reduceByKey
操作对元组RDD进行聚合,对每个单词的计数进行累加。val array: Array[(String, Int)] = wordToSum.collect()
:将最终的RDD收集到驱动程序中,并存储在一个数组中。array.foreach(println)
:遍历数组并打印每个元素,即每个单词及其出现的次数。
关闭Spark上下文:
sc.stop()
:在所有操作完成后,关闭Spark上下文。 这个版本的Word Count示例使用了reduceByKey
来聚合相同键的值,这是Spark中进行单词计数的一种更高效的方法。reduceByKey
会在每个节点上局部聚合数据,然后再进行全局聚合,这样可以减少网络传输的数据量,提高计算效率。
4. Spark03_WordCount3
package com.atguigu.bigdata.spark.core.wc import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} import scala.collection.mutable object Spark03_WordCount { def main(args: Array[String]): Unit = { val sparConf = new SparkConf().setMaster("local").setAppName("WordCount") val sc = new SparkContext(sparConf) wordcount91011(sc) sc.stop() } // groupBy def wordcount1(sc : SparkContext): Unit = { val rdd = sc.makeRDD(List("Hello Scala", "Hello Spark")) val words = rdd.flatMap(_.split(" ")) val group: RDD[(String, Iterable[String])] = words.groupBy(word=>word) val wordCount: RDD[(String, Int)] = group.mapValues(iter=>iter.size) } // groupByKey def wordcount2(sc : SparkContext): Unit = { val rdd = sc.makeRDD(List("Hello Scala", "Hello Spark")) val words = rdd.flatMap(_.split(" ")) val wordOne = words.map((_,1)) val group: RDD[(String, Iterable[Int])] = wordOne.groupByKey() val wordCount: RDD[(String, Int)] = group.mapValues(iter=>iter.size) } // reduceByKey def wordcount3(sc : SparkContext): Unit = { val rdd = sc.makeRDD(List("Hello Scala", "Hello Spark")) val words = rdd.flatMap(_.split(" ")) val wordOne = words.map((_,1)) val wordCount: RDD[(String, Int)] = wordOne.reduceByKey(_+_) } // aggregateByKey def wordcount4(sc : SparkContext): Unit = { val rdd = sc.makeRDD(List("Hello Scala", "Hello Spark")) val words = rdd.flatMap(_.split(" ")) val wordOne = words.map((_,1)) val wordCount: RDD[(String, Int)] = wordOne.aggregateByKey(0)(_+_, _+_) } // foldByKey def wordcount5(sc : SparkContext): Unit = { val rdd = sc.makeRDD(List("Hello Scala", "Hello Spark")) val words = rdd.flatMap(_.split(" ")) val wordOne = words.map((_,1)) val wordCount: RDD[(String, Int)] = wordOne.foldByKey(0)(_+_) } // combineByKey def wordcount6(sc : SparkContext): Unit = { val rdd = sc.makeRDD(List("Hello Scala", "Hello Spark")) val words = rdd.flatMap(_.split(" ")) val wordOne = words.map((_,1)) val wordCount: RDD[(String, Int)] = wordOne.combineByKey( v=>v, (x:Int, y) => x + y, (x:Int, y:Int) => x + y ) } // countByKey def wordcount7(sc : SparkContext): Unit = { val rdd = sc.makeRDD(List("Hello Scala", "Hello Spark")) val words = rdd.flatMap(_.split(" ")) val wordOne = words.map((_,1)) val wordCount: collection.Map[String, Long] = wordOne.countByKey() } // countByValue def wordcount8(sc : SparkContext): Unit = { val rdd = sc.makeRDD(List("Hello Scala", "Hello Spark")) val words = rdd.flatMap(_.split(" ")) val wordCount: collection.Map[String, Long] = words.countByValue() } // reduce, aggregate, fold def wordcount91011(sc : SparkContext): Unit = { val rdd = sc.makeRDD(List("Hello Scala", "Hello Spark")) val words = rdd.flatMap(_.split(" ")) // 【(word, count),(word, count)】 // word => Map[(word,1)] val mapWord = words.map( word => { mutable.Map[String, Long]((word,1)) } ) val wordCount = mapWord.reduce( (map1, map2) => { map2.foreach{ case (word, count) => { val newCount = map1.getOrElse(word, 0L) + count map1.update(word, newCount) } } map1 } ) println(wordCount) } }
这段Scala代码是使用Apache Spark进行词频统计(Word Count)的多个版本的示例。每个版本使用了不同的Spark RDD(弹性分布式数据集)转换操作来实现相同的目标。让我们逐一分析这些版本:
wordcount1:使用
groupBy
对单词进行分组,然后使用mapValues
来计算每个分组的单词数量。wordcount2:使用
groupByKey
对单词进行分组,然后使用mapValues
来计算每个分组的单词数量。这种方法通常不如reduceByKey
高效,因为它会产生更多的网络传输。wordcount3:使用
reduceByKey
来直接对单词进行计数,这是最常见和最高效的Word Count实现方式。wordcount4:使用
aggregateByKey
来对单词进行计数。这个函数允许用户提供一个初始值,并且可以同时提供两个不同的函数来分别处理分区内和分区间的聚合。wordcount5:使用
foldByKey
来对单词进行计数。这个函数是aggregateByKey
的简化版本,其中分区内和分区间的聚合函数是相同的。wordcount6:使用
combineByKey
来对单词进行计数。这个函数提供了对聚合过程的细粒度控制,允许用户为不同的阶段提供不同的函数。wordcount7:使用
countByKey
来直接计算每个键(在这个例子中是单词)的出现次数。这个操作会返回一个Map。wordcount8:使用
countByValue
来直接计算每个唯一值(在这个例子中是单词)的出现次数。这个操作也会返回一个Map。wordcount91011:这个函数演示了如何使用
reduce
来对单词进行计数。它首先将每个单词转换为一个包含单个键值对的Map,然后使用reduce
来合并这些Map,并计算每个单词的总数。 在主函数main
中,只有wordcount91011
被调用。这个函数使用了reduce
来聚合一个由Map组成的RDD,每个Map包含一个单词和它的计数。这个示例展示了如何使用Spark的转换操作来处理复杂的数据结构。 最后,sc.stop()
被调用来关闭Spark上下文。其他函数(wordcount1
到wordcount8
)没有被调用,因此它们中的代码不会被执行。如果需要测试这些函数,可以将它们添加到main
函数中或从命令行调用它们。
5. Linux求Pi值(演示)
进入spark-local:
然后输入下面的代码:
bin/spark-submit \ --class org.apache.spark.examples.SparkPi \ --master local[2] \ ./examples/jars/spark-examples_2.12-3.0.0.jar \ 10
--class 表示要执行程序的主类,此处可以更换为咱们自己写的应用程序
--master local[2] 部署模式,默认为本地模式,数字表示分配的虚拟 CPU 核数量
spark-examples_2.12-3.0.0.jar 运行的应用类所在的 jar 包,实际使用时,可以设定为咱
们自己打的 jar 包
数字 10 表示程序的入口参数,用于设定当前应用的任务数量
得到的结果:
得到的Pi值:
Spark默认端口: 4040
sbin/start-all.sh 启动后,连接web的Spark网址: http:192.168.19.11:8080 #端口号是8080
查看三台机器的进程:
xcall jps
6. 提交参数说明
在提交应用中,一般会同时一些提交参数
bin/spark-submit \ --class <main-class> --master <master-url> \ ... # other options <application-jar> \ [application-arguments]
7. 配置历史服务
其他:
Linux中Spark环境下运行的Scala代码分析:
sc.textFile("data/word.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect
这段Scala代码是使用Apache Spark框架对文本数据进行处理的一个典型例子。让我们逐步分解这段代码的含义:
sc.textFile("data/word.txt")
: 这行代码从本地文件系统读取路径为"data/word.txt"的文本文件,并创建一个RDD(弹性分布式数据集)。RDD是Spark中的基本抽象,代表一个不可变、可分区、可并行操作的元素集合。.flatMap(_.split(" "))
: 这里的flatMap
函数将RDD中的每个文本行(字符串)转换为一个单词数组。_.split(" ")
是一个匿名函数,它将每行文本按照空格分割成单词数组。flatMap
确保这些单词数组被扁平化,即所有单词都被放入一个大的 RDD 中,而不是一个数组RDD。.map((_,1))
: 这行代码将RDD中的每个单词转换为一个元组(tuple),其中第一个元素是单词本身,第二个元素是数字1。这个操作是为了为后续的reduceByKey
操作做准备,这种操作通常用于计数。.reduceByKey(_+_)
: 这里的reduceByKey
函数对RDD中相同键(在这个例子中是单词)的值进行聚合。对于每个键,reduceByKey
会应用一个函数(在这个例子中是_+_
,即两个值相加),从而得到每个单词的出现次数。.collect
: 最后,collect
函数是一个行动(action),它会触发Spark作业的执行,并将结果RDD的所有元素加载到驱动程序(driver)的内存中,返回一个数组。在这个例子中,它会返回一个包含所有单词及其出现次数的数组。
综上所述,这段代码的作用是读取一个文本文件,计算每个单词出现的次数,并将结果返回给驱动程序。请注意,在实际的Spark集群上运行时,这段代码会并行处理数据,因此非常适合处理大规模数据集。
02-240602-Spark笔记
第 3 章 Spark 运行环境:
3.1.部署模式对比:
3.2.端口号:
第 4 章 Spark 运行架构:
4.1 运行架构:
Spark 框架的核心是一个计算引擎,整体来说,它采用了标准 master-slave 的结构。
如下图所示,它展示了一个 Spark 执行时的基本结构。图形中的 Driver 表示 master,
负责管理整个集群中的作业任务调度。图形中的 Executor 则是 slave,负责实际执行任务。
4.2 核心组件:
由上图可以看出,对于 Spark 框架有两个核心组件:
4.2.1 Driver:
Spark 驱动器节点,用于执行 Spark 任务中的 main 方法,负责实际代码的执行工作。
Driver 在 Spark 作业执行时主要负责:
➢ 将用户程序转化为作业(job)
➢ 在 Executor 之间调度任务(task)
➢ 跟踪 Executor 的执行情况
➢ 通过 UI 展示查询运行情况
实际上,我们无法准确地描述 Driver 的定义,因为在整个的编程过程中没有看到任何有关
Driver 的字眼。所以简单理解,所谓的 Driver 就是驱使整个应用运行起来的程序,也称之为
Driver 类。
4.2.2 Executor:
Spark Executor 是集群中工作节点(Worker)中的一个 JVM 进程,负责在 Spark 作业
中运行具体任务(Task),任务彼此之间相互独立。Spark 应用启动时,Executor 节点被同
时启动,并且始终伴随着整个 Spark 应用的生命周期而存在。如果有 Executor 节点发生了
故障或崩溃,Spark 应用也可以继续执行,会将出错节点上的任务调度到其他 Executor 节点
上继续运行。
Executor 有两个核心功能:
➢ 负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程
➢ 它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存
式存储。RDD 是直接缓存在 Executor 进程内的,因此任务可以在运行时充分利用缓存
数据加速运算。
4.2.3 Master & Worker :
Spark 集群的独立部署环境中,不需要依赖其他的资源调度框架,自身就实现了资源调
度的功能,所以环境中还有其他两个核心组件:Master 和 Worker,这里的 Master 是一个进
程,主要负责资源的调度和分配,并进行集群的监控等职责,类似于 Yarn 环境中的 RM, 而
Worker 呢,也是进程,一个 Worker 运行在集群中的一台服务器上,由 Master 分配资源对
数据进行并行的处理和计算,类似于 Yarn 环境中 NM。
4.2.4 ApplicationMaster:
Hadoop 用户向 YARN 集群提交应用程序时,提交程序中应该包含 ApplicationMaster,用
于向资源调度器申请执行任务的资源容器 Container,运行用户自己的程序任务 job,监控整
个任务的执行,跟踪整个任务的状态,处理任务失败等异常情况。
说的简单点就是,ResourceManager(资源)和 Driver(计算)之间的解耦合靠的就是
ApplicationMaster。
4.2.5 代码透解:
组成部分:
Driver 代码:
package com.atguigu.bigdata.spark.core.test import java.io.{ObjectOutputStream, OutputStream} import java.net.Socket object Driver { def main(args: Array[String]): Unit = { // 连接服务器 val client1 = new Socket("localhost",9999) val client2 = new Socket("localhost",8888) val task = new Task() val out1: OutputStream = client1.getOutputStream val objOut1 = new ObjectOutputStream(out1) val subTask = new SubTask() subTask.logic = task.logic subTask.datas = task.datas.take(2) objOut1.writeObject(subTask) objOut1.flush() objOut1.close() client1.close() val out2 : OutputStream = client2.getOutputStream val objOut2 = new ObjectOutputStream(out2) val subTask1 = new SubTask() subTask1.logic = task.logic subTask1.datas = task.datas.takeRight(2) objOut2.writeObject(subTask1) objOut2.flush() objOut2.close() client2.close() println("客户端数据发送完毕") } }
Executor 代码:
package com.atguigu.bigdata.spark.core.test import java.io.{InputStream, ObjectInputStream} import java.net.{ServerSocket, Socket} object Executor { def main(args: Array[String]): Unit = { //启动服务器,接收数据 val server = new ServerSocket(9999) println("服务器启动,等待接收数据") // 等待客户端的连接 val client : Socket = server.accept() val in : InputStream = client.getInputStream val objIn = new ObjectInputStream(in) val task : SubTask = objIn.readObject().asInstanceOf[SubTask] val ints:List[Int] = task.compute() println("计算节点[9999]计算的结果为: "+ ints) objIn.close() client.close() server.close() } }
Executor2 代码:
package com.atguigu.bigdata.spark.core.test import java.io.{InputStream, ObjectInputStream} import java.net.{ServerSocket, Socket} object Executor2 { def main(args: Array[String]): Unit = { // 启动服务器,接收数据 val server = new ServerSocket(8888) println("服务器启动,等待接收数据") // 等待客户端的连接 val client: Socket = server.accept() val in: InputStream = client.getInputStream val objIn = new ObjectInputStream(in) val task: SubTask = objIn.readObject().asInstanceOf[SubTask] val ints: List[Int] = task.compute() println("计算节点[8888]计算的结果为:" + ints) objIn.close() client.close() server.close() } }
Task 代码(Scala类):
package com.atguigu.bigdata.spark.core.test class Task extends Serializable { val datas = List(1,2,3,4) // val logic = (num:Int) => {num*2} val logic : (Int)=>Int = _ * 2 }
SubTask 代码(Scala类):
package com.atguigu.bigdata.spark.core.test class SubTask extends Serializable { var datas : List[Int] = _ var logic : (Int)=>Int = _ // 计算 def compute() = { datas.map(logic) } }
运行结果:
启动Executor和Executor2:
启动Driver:
查看Executor和Executor2:
4.3 核心概念:
4.3.1 Executor 与 Core:
Spark Executor 是集群中运行在工作节点(Worker)中的一个 JVM 进程,是整个集群中
的专门用于计算的节点。在提交应用中,可以提供参数指定计算节点的个数,以及对应的资
源。这里的资源一般指的是工作节点 Executor 的内存大小和使用的虚拟 CPU 核(Core)数
量。应用程序相关启动参数如下:
4.3.2 并行度(Parallelism)
在分布式计算框架中一般都是多个任务同时执行,由于任务分布在不同的计算节点进行
计算,所以能够真正地实现多任务并行执行,记住,这里是并行,而不是并发。这里我们将
整个集群并行执行任务的数量称之为并行度。那么一个作业到底并行度是多少呢?这个取决
于框架的默认配置。应用程序也可以在运行过程中动态修改。
4.3.3 有向无环图(DAG)
大数据计算引擎框架我们根据使用方式的不同一般会分为四类,其中第一类就是
Hadoop 所承载的 MapReduce,它将计算分为两个阶段,分别为 Map 阶段 和 Reduce 阶段。
对于上层应用来说,就不得不想方设法去拆分算法,甚至于不得不在上层应用实现多个 Job
的串联,以完成一个完整的算法,例如迭代计算。 由于这样的弊端,催生了支持 DAG 框
架的产生。因此,支持 DAG 的框架被划分为第二代计算引擎。如 Tez 以及更上层的
Oozie。这里我们不去细究各种 DAG 实现之间的区别,不过对于当时的 Tez 和 Oozie 来
说,大多还是批处理的任务。接下来就是以 Spark 为代表的第三代的计算引擎。第三代计
算引擎的特点主要是 Job 内部的 DAG 支持(不跨越 Job),以及实时计算。
这里所谓的有向无环图,并不是真正意义的图形,而是由 Spark 程序直接映射成的数据
流的高级抽象模型。简单理解就是将整个程序计算的执行过程用图形表示出来,这样更直观,
更便于理解,可以用于表示程序的拓扑结构。
DAG(Directed Acyclic Graph)有向无环图是由点和线组成的拓扑图形,该图形具有方
向,不会闭环。
4.4 提交流程:
所谓的提交流程,其实就是我们开发人员根据需求写的应用程序通过 Spark 客户端提交
给 Spark 运行环境执行计算的流程。在不同的部署环境中,这个提交过程基本相同,但是又
有细微的区别,我们这里不进行详细的比较,但是因为国内工作中,将 Spark 引用部署到
Yarn 环境中会更多一些,所以本课程中的提交流程是基于 Yarn 环境的。
Spark 应用程序提交到 Yarn 环境中执行的时候,一般会有两种部署执行的方式:Client
和 Cluster。两种模式主要区别在于:Driver 程序的运行节点位置。
4.4.1 Yarn Client 模式
Client 模式将用于监控和调度的 Driver 模块在客户端执行,而不是在 Yarn 中,所以一
般用于测试。
➢ Driver 在任务提交的本地机器上运行
➢ Driver 启动后会和 ResourceManager 通讯申请启动 ApplicationMaster
➢ ResourceManager 分配 container,在合适的 NodeManager 上启动 ApplicationMaster,负
责向 ResourceManager 申请 Executor 内存
➢ ResourceManager 接到 ApplicationMaster 的资源申请后会分配 container,然后
ApplicationMaster 在资源分配指定的 NodeManager 上启动 Executor 进程
➢ Executor 进程启动后会向 Driver 反向注册,Executor 全部注册完成后 Driver 开始执行
main 函数
➢ 之后执行到 Action 算子时,触发一个 Job,并根据宽依赖开始划分 stage,每个 stage 生
成对应的 TaskSet,之后将 task 分发到各个 Executor 上执行。
4.4.2 Yarn Cluster 模式
Cluster 模式将用于监控和调度的 Driver 模块启动在 Yarn 集群资源中执行。一般应用于
实际生产环境。
➢ 在 YARN Cluster 模式下,任务提交后会和 ResourceManager 通讯申请启动
ApplicationMaster,
➢ 随后 ResourceManager 分配 container,在合适的 NodeManager 上启动 ApplicationMaster,
此时的 ApplicationMaster 就是 Driver。
➢ Driver 启动后向 ResourceManager 申请 Executor 内存,ResourceManager 接到
ApplicationMaster 的资源申请后会分配 container,然后在合适的 NodeManager 上启动
Executor 进程
➢ Executor 进程启动后会向 Driver 反向注册,Executor 全部注册完成后 Driver 开始执行
main 函数,
➢ 之后执行到 Action 算子时,触发一个 Job,并根据宽依赖开始划分 stage,每个 stage 生
成对应的 TaskSet,之后将 task 分发到各个 Executor 上执行。