spark02_学习笔记
1、目标
- 1、掌握RDD的底层原理
- 2、掌握RDD的常用的算子操作
- 3、掌握RDD的宽窄依赖
- 4、掌握RDD的缓存机制
- 5、掌握划分stage
- 6、掌握spark任务运行架构和调度流程
2、RDD概述
2.1 什么是RDD
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。
Dataset:它是一个集合,集合里面有很多个元素
Distributed:rdd中的数据是进行了分布式存储,后期方便于进行分布式计算。
Resilient:弹性,可以意味着rdd的数据可以保存在内存或者是磁盘中。
2.2 RDD的五大属性
(1) A list of partitions
一个分区列表
它表示一个rdd中有很多个分区,后期spark任务的计算是以分区为单位进行计算。一个分区就对应上一个task线程。
val rdd=sc.textFile(文件)
该文件的block个数小于等于2,这个时候rdd的分区数就是2
该文件的block个数大于2, 这个时候rdd的分区数就跟block个数相等
(2) A function for computing each split
作用在每一个分区中的函数
var rdd2=rdd1.map(x=>(x,1))
(3) A list of dependencies on other RDDs
一个RDD会依赖于其他多个RDD,这里就涉及到RDD与RDD之间的依赖关系,后期spark任务的容错机制就是根据这个特性而来。
var rdd2=rdd1.map(x=>(x,1))
(4) Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
(可选性) 一个分区函数,针对于一个kv类型的RDD才会有分区函数(必须要产生shuffle)。对于不是kv类型的RDD,它的分区函数是None. spark提供了2种shuffle机制,第一种默认值:hashPartitioner -------> key.hashcode % 分区数=分区号,
第二种RangePartitioner: 基于一个范围的分区策略。
(5) Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)
(可选性) 一个优先的数据分区列表,这里就涉及到数据本地性和数据位置最优(指的是哪个节点上有数据 , 就优先考虑哪个节点)。
spark后期再进行任务分配的时候,会优先考虑存有数据的worker节点来进行任务的计算。
当任务运行到reduceByKey的时候 , 此时产生了shuffle , 因此每个shuffle中的内容有可能来自于多个分区 .
2.3 创建RDD
- 1、通过一个已经存在的scala集合去构建
val rdd1=sc.parallelize(List(1,2,3,4),2)
- 2、通过加载外部的数据源去构建
val rdd2=sc.textFile("/words.txt")
- 3、通过一个已经存在的rdd去构建
val rdd3=rdd2.flatMap(x=>x.split(" "))
3、RDD中算子操作
-
rdd中算子一共可以分为2类
-
transformation(转换)
- 它是一个转换,可以实现把一个rdd转换生成一个新的rdd,它不会立即触发任务的运行,它是延迟加载。
- 它只是记录下作用在rdd的上转换操作
- 比如
flatMap/map/reduceByKey/sortBy
- 它是一个转换,可以实现把一个rdd转换生成一个新的rdd,它不会立即触发任务的运行,它是延迟加载。
-
action (动作)
-
它会触发任务的真正运行
-
比如
collect/saveAsTextFile
-
-
- 常用的Transformation:
转换 | 含义 |
---|---|
map(func) | 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成 |
filter(func) | 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成 |
flatMap(func) | 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素) |
mapPartitions(func) | 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U] |
mapPartitionsWithIndex(func) | 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是 (Int, Interator[T]) => Iterator[U] |
union(otherDataset) | 对源RDD和参数RDD求并集后返回一个新的RDD |
intersection(otherDataset) | 对源RDD和参数RDD求交集后返回一个新的RDD |
distinct([numTasks])) | 对源RDD进行去重后返回一个新的RDD |
groupByKey([numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD |
reduceByKey(func, [numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置 |
sortByKey([ascending], [numTasks]) | 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD |
sortBy(func,[ascending], [numTasks]) | 与sortByKey类似,但是更灵活 |
join(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD |
cogroup(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD |
coalesce(numPartitions) | 减少 RDD 的分区数到指定值。 |
repartition(numPartitions) | 重新给 RDD 分区 |
repartitionAndSortWithinPartitions(partitioner) | 重新给 RDD 分区,并且每个分区内以记录的 key 排序 |
- 常用Action类
动作 | 含义 |
---|---|
reduce(func) | reduce将RDD中元素前两个传给输入函数,产生一个新的return值,新产生的return值与RDD中下一个元素(第三个元素)组成两个元素,再被传给输入函数,直到最后只有一个值为止。 |
collect() | 在驱动程序中,以数组的形式返回数据集的所有元素 |
count() | 返回RDD的元素个数 |
first() | 返回RDD的第一个元素(类似于take(1)) |
take(n) | 返回一个由数据集的前n个元素组成的数组 |
takeOrdered(n, [ordering]) | 返回自然顺序或者自定义顺序的前 n 个元素 |
saveAsTextFile(path) | 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本 |
saveAsSequenceFile(path) | 将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。 |
saveAsObjectFile(path) | 将数据集的元素,以 Java 序列化的方式保存到指定的目录下 |
countByKey() | 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。 |
foreach(func) | 在数据集的每一个元素上,运行函数func |
foreachPartition(func) | 在数据集的每一个分区上,运行函数func |
-
RDD常用的算子操作
Spark Rdd的所有算子操作,请见《sparkRDD函数详解》
启动spark-shell 进行测试:
spark-shell --master spark://node1:7077
也可本地操作
spark-shell --master local[2]
练习1:map、filter
//通过并行化生成rdd val rdd1 = sc.parallelize(List(5, 6, 4, 7, 3, 8, 2, 9, 1, 10)) //对rdd1里的每一个元素乘2然后排序 val rdd2 = rdd1.map(_ * 2).sortBy(x => x, true) //过滤出大于等于5的元素 val rdd3 = rdd2.filter(_ >= 5) //将元素以数组的方式在客户端显示 rdd3.collect
练习2:flatMap
val rdd1 = sc.parallelize(Array("a b c", "d e f", "h i j")) //将rdd1里面的每一个元素先切分在压平 val rdd2 = rdd1.flatMap(_.split(" ")) rdd2.collect
练习3:交集、并集
val rdd1 = sc.parallelize(List(5, 6, 4, 3)) val rdd2 = sc.parallelize(List(1, 2, 3, 4)) //求并集 val rdd3 = rdd1.union(rdd2) //求交集 val rdd4 = rdd1.intersection(rdd2) //去重 rdd3.distinct.collect rdd4.collect
练习4:join、groupByKey
val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2))) val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 1), ("shuke", 2))) //求join val rdd3 = rdd1.join(rdd2) rdd3.collect //求并集 val rdd4 = rdd1 union rdd2 rdd4.collect //按key进行分组 val rdd5=rdd4.groupByKey rdd5.collect
练习5:cogroup
val rdd1 = sc.parallelize(List(("tom", 1), ("tom", 2), ("jerry", 3), ("kitty", 2))) val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 1), ("jim", 2))) //cogroup val rdd3 = rdd1.cogroup(rdd2) //注意cogroup与groupByKey的区别 rdd3.collect
练习6:reduce
val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5)) //reduce聚合 val rdd2 = rdd1.reduce(_ + _) rdd2.collect
练习7:reduceByKey、sortByKey
val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 3), ("kitty", 2), ("shuke", 1))) val rdd2 = sc.parallelize(List(("jerry", 2), ("tom", 3), ("shuke", 2), ("kitty", 5))) val rdd3 = rdd1.union(rdd2) //按key进行聚合 val rdd4 = rdd3.reduceByKey(_ + _) rdd4.collect //按value的降序排序 val rdd5 = rdd4.map(t => (t._2, t._1)).sortByKey(false).map(t => (t._2, t._1)) rdd5.collect
练习8:repartition、coalesce
val rdd1 = sc.parallelize(1 to 10,3) //利用repartition改变rdd1分区数 //减少分区 rdd1.repartition(2).partitions.size //增加分区 rdd1.repartition(4).partitions.size //利用coalesce改变rdd1分区数 //减少分区 rdd1.coalesce(2).partitions.size
注意:repartition可以增加和减少rdd中的分区数 , 可用于小文件的整合 , 将多个小文件放在一个分区中,coalesce只能减少rdd分区数,增加rdd分区数不会生效。
4、通过spark实现点击流日志分析案例
4.1 统计PV
package cn.itcast.rdd
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
//todo:利用spark实现点击流日志数据分析----------------PV
object PV {
def main(args: Array[String]): Unit = {
//1、创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("PV").setMaster("local[2]")
//2、创建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、读取数据文件
val data: RDD[String] = sc.textFile("E:\\data\\access.log")
//4、统计pv
val pv: Long = data.count()
println("PV:"+pv)
//5、关闭
sc.stop()
}
}
4.2 统计UV
package cn.itcast.rdd
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
//todo:利用spark实现点击流日志数据分析----------------UV
object UV {
def main(args: Array[String]): Unit = {
//1、创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("UV").setMaster("local[2]")
//2、创建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、读取数据文件
val data: RDD[String] = sc.textFile("E:\\data\\access.log")
//4、切分每一行,获取所有的ip地址
val ips: RDD[String] = data.map(x=>x.split(" ")(0))
//5、根据ip地址去重
val distinctRDD: RDD[String] = ips.distinct()
//6、统计UV
val uv: Long = distinctRDD.count()
println("UV:"+uv)
//7、关闭
sc.stop()
}
}
4.3 统计TopN
package cn.itcast.rdd
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
//todo:利用spark实现点击流日志数据分析----------------TopN(把访问url出现次数最多的前N位获取得到)
object TopN {
def main(args: Array[String]): Unit = {
//1、创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("TopN").setMaster("local[2]")
//2、创建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、读取数据文件
val data: RDD[String] = sc.textFile("E:\\data\\access.log")
//4、先过滤出丢失的字段的记录
val urlAndOne: RDD[(String, Int)] = data.filter(x=>x.split(" ").length >10).map(x=>x.split(" ")(10)).map(x=>(x,1))
//5、相同url出现的1累加
val result: RDD[(String, Int)] = urlAndOne.reduceByKey(_+_)
//将输出的结果去掉"-"的项
val fresult: RDD[(String, Int)] = result.filter(x=>x._1!="\"-\"")
//6、按照url出现的次数降序
val sortedRDD: RDD[(String, Int)] = fresult.sortBy(x=>x._2,false)
//7、取出出现次数最多的前5位
val finalResult: Array[(String, Int)] = sortedRDD.take(5)
finalResult.foreach(println)
//8、关闭
sc.stop()
}
}
5、通过spark实现ip地址查询
package cn.itcast.rdd
import java.sql.{Connection, DriverManager, PreparedStatement}
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
//todo:利用spark来实现ip地址查询
object Iplocation {
//把String类型的ip转换成Long类型的数字 192.168.200.100
def ip2Long(ip: String): Long ={
val ips: Array[String] = ip.split("\\.")
var ipNum:Long=0L
for(i <- ips){
ipNum= i.toLong | ipNum << 8
}
ipNum
}
def binarySearch(ipNum: Long, broadCastValue: Array[(String, String, String, String)]): Int = {
var start=0
var end=broadCastValue.length-1
while(start <= end){
val middle=(start+end)/2
if(ipNum >=broadCastValue(middle)._1.toLong && ipNum <= broadCastValue(middle)._2.toLong){
return middle
}
if(ipNum < broadCastValue(middle)._1.toLong){
end=middle-1
}
if(ipNum >broadCastValue(middle)._2.toLong){
start=middle+1
}
}
-1
}
//把数据写入到mysql表中
def data2mysql(iter:Iterator[((String,String), Int)])= {
//定义数据库连接
var conn:Connection=null
//定义PrepareStatement
var ps:PreparedStatement=null
//定义sql语句
val sql="insert into iplocation(longitude,latitude,total_count) values(?,?,?)"
//获取数据库连接
conn= DriverManager.getConnection("jdbc:mysql://192.168.200.100:3306/spark","root","123456")
//获取PrepareStatement
ps=conn.prepareStatement(sql)
//遍历迭代器
try {
iter.foreach(line => {
//给?占位符赋值
ps.setString(1, line._1._1)
ps.setString(2, line._1._2)
ps.setLong(3, line._2)
ps.execute()
})
} catch {
case e:Exception => println(e)
} finally {
if(ps!=null){
ps.close()
}
if(conn!=null){
conn.close()
}
}
}
def main(args: Array[String]): Unit = {
//1、创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("Iplocation").setMaster("local[2]")
//2、创建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、读取城市ip信息数据文件 获取ip开始数字,ip结束数字,经度,维度
val city_ip_rdd: RDD[(String, String, String, String)] = sc.textFile("E:\\data\\ip.txt").map(x=>x.split("\\|")).map(x=>(x(2),x(3),x(x.length-2),x(x.length-1)))
//把城市ip信息数据通过广播变量下发到每一个worker节点 ,broadcast方法中需要给定rdd的真实数据,不能够直接把rdd传进去
val city_ip_broadcast: Broadcast[Array[(String, String, String, String)]] = sc.broadcast(city_ip_rdd.collect())
//4、读取运营商日志数据 获取所有的ip地址
val ips: RDD[String] = sc.textFile("E:\\data\\20090121000132.394251.http.format").map(x=>x.split("\\|")(1))
//5、遍历ips,获取每一个ip,然后把ip地址转换成Long类型,后期去匹配
val result: RDD[((String, String), Int)] = ips.mapPartitions(iter => {
//获取广播变量的值
val broadCastValue: Array[(String, String, String, String)] = city_ip_broadcast.value
//遍历迭代器,获取每一个ip地址
iter.map(ip => {
//把ip地址转换成Long类型数字
val ipNum: Long = ip2Long(ip)
//通过二分查询获取ipNum在broadCastValue数组中的下标
val index: Int = binarySearch(ipNum, broadCastValue)
//获取下标对应的元素
val value: (String, String, String, String) = broadCastValue(index)
//返回结果数据 ((经度,维度),1)
((value._3, value._4), 1)
})
})
//6、把相同经度和维度出现的1累加
val finalResult: RDD[((String, String), Int)] = result.reduceByKey(_+_)
//7、打印结果数据
finalResult.foreach(println)
//把结果数据写入到mysql表中
finalResult.foreachPartition(data2mysql)
//8、关闭
sc.stop()
}
}
扩展 : 广播变量图解
广播变量极大地减少了内存的开销 , 保证了程序的正常运行
6、RDD的依赖关系
-
rdd与rdd之间有依赖关系
-
窄依赖
窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用 总结:窄依赖我们形象的比喻为独生子女 窄依赖不会产生shuffle flatMap filter map....
-
宽依赖
宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition 总结:宽依赖我们形象的比喻为超生 宽依赖会产生shuffle reduceByKey groupByKey sortBy....
-
7、lineage(血统)
rdd后期会进行大量的转换操作,我们把rdd的这些操作行为记录下来,记录下来的信息我们就称为lineage(血统)
血统好处:
当前某一个rdd的分区数据丢失了,可以通过血统这一层关系来重新计算恢复得到。这里spark的任务容错机制就是根据血统而来。
8、RDD的缓存机制
8.1 RDD的缓存是什么
可以把一个rdd的结果数据进行缓存,后续有其他的job需要依赖于前面rdd的结果数据,这个时候可以直接从缓存中获取得到,避免重复计算。
8.2 如何设置缓存
rdd中提供了设置缓存的2种方式
cache:默认是将数据缓存在内存中,其本质是调用persist方法
设置方式 : rdd.cache
persist:可以把数据缓存内存或者是磁盘中,它里面可以设置丰富的缓存级别,这些缓存级别都封装
在一个object 中,这个object的名称Storagelevel
设置方式 : rdd.persist(org.apache.spark.storage.StorageLevel)
以上这个2个方法并不是调用之后就立即执行,后续是需要一个action操作,才会触发缓存真正执行。
8.3 如何清除缓存
调用rdd的unpersist 清除缓存数据。
手动清除缓存:rdd1.unpersist(true)
自动清除:对于程序来说,如果我们设置了缓存,后期程序结束了,它会自动清除
8.4 什么时候设置缓存
-
1、一个rdd后期被使用了多次
val rdd2=rdd1.flatMap(_.split(" ")) val rdd3=rdd1.map(x=>(x,1)) 上面的rdd1被使用了多次,每一次使用都需要先把rdd1的结果数据先计算一下,这个时候就可以对rdd1设置缓存,避免后续的rdd需要前面的结果。
-
2、某一个rdd的数据来之不易
val rdd2=rdd1.flatMap(_.split(" ")).map().xxxxx.xxxxxxx.xxxxxxx.xxxxxxx.xxx.xxx
9、DAG有向无环图和划分stage
9.1 什么是DAG
DAG就是按照rdd的一系列操作最后生成了一个有方向无闭环的图,这个图我们就称为DAG有向无环图。按照操作逻辑划分成不同的stage(不同的调度阶段)
(1) 为什么要划分stage?
在每一个stage中都是窄依赖 , 并没有宽依赖 , spark的任务task就是以分区为单位 , 这些task都是可以并行的运行 , 互不影响
(2) 如何划分stage? 划分stage的依据就是宽依赖
- 从最后一个rdd往前推 , 先创建一个stage , 然后把最后一个rdd加入到该stage中 , 它就是最后一个stage
- 如果遇到了窄依赖 , 就把该rdd加入到本stage中 , 如果遇到了宽依赖 , 就从宽依赖切开 , 最后一个stage也就结束了
- 重新创建一个新的stage , 按照第二步的操作 , 继续往前推 , 一直推到开始 , 整个划分stage也就结束了
(3) stage的内部逻辑
每一个stage中都有很多可以并行运行的task , 这些task被封装在一个taskSet集合中
扩展 : rdd与rdd之间有依赖关系 , stage与stage之间也有依赖关系 , 比如前面的stage中的task先运行 , 后面的stage中的task后运行 , 也就是说stage中输入数据是前面stage输出结果
后期开发好的代码中 , 一个action就是一个job , 一个application中包括了客户端的所有代码 , 也就是说一个application中很多个job , 一个job中会存在rdd的一系列操作 , 一个job会生成一个DAG有向无环图 , 一个job中有可能有多个宽依赖 , 按照宽依赖切分 , 这个时候也就意味着一个job中有很多个stage , 每一个stage内部都有很多可以并行跑的task
10、spark任务调度
(1)Driver会运行客户端main方法中的代码,代码就会构建SparkContext对象,在构建SparkContext对象中,会创建DAGScheduler和TaskScheduler,然后按照rdd一系列的操作生成DAG有向无环图。最后把DAG有向无环图提交给DAGScheduler。
(2)DAGScheduler拿到DAG有向无环图后,按照宽依赖进行stage的划分,这个时候会产生很多个stage,每一个stage中都有很多可以并行运行的task,把每一个stage中这些task封装在一个taskSet集合中,最后提交给TaskScheduler。
(3)TaskScheduler拿到taskSet集合后,依次遍历每一个task,最后提交给worker节点的exectuor进程中。task就以线程的方式运行在worker节点的executor进程中。
11、spark的容错机制之checkpoint
11.1 什么是checkpoint
对rdd设置缓存有2种方式:
(1)cache:默认是把数据缓存在内存中,后续操作起来速度比较快,但是由于进程或者是服务器挂掉了,这个时候内存中的数据肯定是丢失,也就是说cache不是非常安全,数据丢失的概率比较大。
(2)persist:有丰富的缓存级别,可以把数据缓存在磁盘中,然后需要用到该数据,可以进行磁盘io操作获取得到,这一点比cache速度会慢点,但是比cache安全点,这里同样也有数据丢失的可能性(磁盘损坏、系统管理员由于误操作把本地数据清除掉了)
checkpoint:它是提供了一个相对而言更加可靠的持久化数据的方式,它可以把rdd的数据写入到分布式文件系统(HDFS)去保存,利用了hdfs高可靠,多个副本机制最大程度保证数据不丢失。
11.2 如何使用checkpoint
- 1、通过sparkContext对象设置checkpoint目录,用于保存rdd的数据
sc.setCheckpointDir("/ck2018")
- 2、对需要持久化的rdd调用一个方法checkpoint方法
val rdd1=sc.textFile("/words.txt")
rdd1.checkpoint
- 3、后续需要有个action操作,触发checkpoint的执行
rdd1.collect
11.3 cache/persist/checkpoint区别
cache和persist:
这2个方法都可以将rdd的数据进行缓存,后续都要有一个action操作,才会触发缓存任务的执行,它不会改变rdd的血统。整个程序结束之后,这些缓存数据自己被清除了。
checkpoint:
可以把数据持久化到hdfs上,这个时候先rdd.checkpoint操作,然后也需要一个action。
一个action操作就是一个job,在这里首先它会执行action这个job,执行完成之后,它会开启一个新的job来执行checkpoint操作,也就是说在这里比cache和persist多了一个job。它会改变rdd的血统。
11.4 数据丢失之后的恢复顺序
1、首先看一写有没有设置cache,如果有,直接从cache获取得到
2、如果没有cache,看一下有没有做checkpoint,如果有就直接从checkpoint获取得到
3,如果没有checkpoint,利用血统这层关系来重新计算恢复得到。
12、spark的运行架构
-
构建Spark Application的运行环境(启动SparkContext),SparkContext向资源管理器(可以是Standalone、Mesos或YARN)注册并申请运行Executor资源;
-
资源管理器分配Executor资源并启动Executor,Executor运行情况将随着心跳发送到资源管理器上;
-
SparkContext构建成DAG图,将DAG图分解成Stage,并把Taskset发送给Task Scheduler。Executor向SparkContext申请Task,Task Scheduler将Task发放给Executor运行同时SparkContext将应用程序代码发放给Executor。
-
Task在Executor上运行,运行完毕释放所有资源。
13. Spark运行架构特点
Spark运行架构特点:
- 每个Application获取专属的executor进程,该进程在Application期间一直驻留,并以多线程方式运行tasks。
- Spark任务与资源管理器无关,只要能够获取executor进程,并能保持相互通信就可以了。
- 提交SparkContext的Client应该靠近Worker节点(运行Executor的节点),最好是在同一个Rack里,因为Spark程序运行过程中SparkContext和Executor之间有大量的信息交换;如果想在远程集群中运行,最好使用RPC将SparkContext提交给集群,不要远离Worker运行SparkContext。
- Task采用了数据本地性和推测执行的优化机制。