RDD的类型总结
Spark中的RDD(Resilient Distributed Dataset)就是弹性分布式数据集,是Spark中基本的数据抽象。每个RDD都被分为多个分区,这些分区运行在集群中的不同节点。创建RDD的两种方式:读取一个外部数据集,或者在驱动器里面分发驱动器程序中的对象集合。(在任何时候都能够进行重算是我们为什么把RDD描述为"弹性"的原因--来自Spark快速大数据分析 p22)
RDD支持两种类型的操作:转化操作(transformation)和行动操作(action),转化操作会返回一个新的RDD(惰性计算的,只有第一次在行动操作中用到时才会真正计算)
步入正题:RDD的五大特性(来自spark-core_2.11-2.20-sources.jar RDD.scala)
/**
- A list of partitions
- A function for computing each split
- A list of dependencies on other RDDs
- Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
- Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)
*/
翻译过来就是
1.一组分片(Partition),即数据集的基本组成单位。对于RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户在创建RDD时指定RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认制。默认值就是所分配的 CPU core的数目(也有讲到,分片数不能少于blocks数量)
2.一个计算每个分区的函数。Spark中的RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。(RDD抽象类要求其所有子类都必须实现compute方法,该方法介绍的参数之一是一个Partition对象,目的是计算该分区中的数据)
3.RDD之间的依赖关系。RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。(参照下面说的宽窄依赖)
4.一个Partitioner,即RDD的分片函数。当前Spark中实现类两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。(只有对于k-v类型的RDD才会有Partitioner,非k-v得RDD的Partitioner的值是None)。Partitioner函数不但决定了本身的分片数量,也决定了parent RDD Shuffle 输出时的分片数量。
5.一个列表,存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。
RDD的依赖关系的理解
RDD中有两种两种依赖关系:窄依赖和宽依赖。
窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用(也有人形象的比喻为独生子女)
宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition(也有人形象的比喻为超生)
学科pv案例的实现
import java.net.URL
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 实现思路
* 1.计算出每个学科的各个模块的访问量
* 2.按照学科进行分组
* 3.组内排序,并取topN
*/
object SubjectAccessCount {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SubjectAccessCount").setMaster("local[2]")
val sc = new SparkContext(conf)
//获取数据
val logs = sc.textFile("D:\\data/subjectaccess")
//将用户访问日志进行切分并返回url
val url: RDD[String] = logs.map(line => line.split("\t")(1))
//将url生成元组,便于聚合
val tupUrl: RDD[(String, Int)] = url.map((_, 1))
//获取每个学科的各个模块的访问量
val reduceUrl: RDD[(String, Int)] = tupUrl.reduceByKey(_ + _)
//通过上面的数据来获取学科信息
val subjectAndUrlInfo: RDD[(String, (String, Int))] = reduceUrl.map(tup => {
val url = tup._1 //用户请求的url
val count = tup._2 //url对应的pv
val subject = new URL(url).getHost
(subject, (url, count))
})
//按照学科信息进行分组
val grouped: RDD[(String, Iterable[(String, Int)])] = subjectAndUrlInfo.groupByKey
//组内降序排序
val sorted: RDD[(String, List[(String, Int)])] = grouped.mapValues(_.toList.sortBy(_._2).reverse)
//求topN
val res: RDD[(String, List[(String, Int)])] = sorted.mapValues(_.take(3))
// println(subjectAndUrlInfo.collect.toBuffer)
println(res.collect.toBuffer)
sc.stop()
}
}
缓存的应用场景(持久化)
使用最多的是MEMORY_ONLY 和 MEMORY_AND_DISK(内存不够会溢写到磁盘)
Spark RDD是转换算子是惰性求值的,而有时候我们希望能够多次使用同一个RDD。如果简单的对RDD调用行动操作,Spark每次都会重算RDD以及它的所有依赖。这个迭代算法中消耗格外大,为了避免多次计算同一个RDD,可以让Spark对数据进行持久化。当我们持久化存储一个RDD时,计算出RDD的节点会分别保存他们所求出的分区的数据。如果希望节点故障的情况下,不会拖累执行速度,我们可以把数据备份到多个节点。--(可以通过在存储级别的末尾加上 "_2" 来持久化数据分为两份)
persist()方法调用本身不会强制求值。
如果要缓存的数据过多,内存放不下就要使用LRU缓存策略把最老的分区从内存中移除(MEMORY_ONLY) 但是对于使用使用内存和磁盘的被移除的分区都会被写入磁盘。
关于Driver端和Executor端执行逻辑的区分
Driver(驱动器节点)时执行程序main()方法的进程,执行用户编写的用来创建SparkContext,创建RDD,以及进行RDD的转换操作和行动操作的代码,在Spark shell中会预先加载SparkContext对象。驱动器在Spark中有两个职责:
1.把用户程序转换为任务
2.为执行器节点调度任务
执行器节点是一个工作进程,负责在Spark作业中运行任务,任务间相互独立。Spark应用启动时,执行节点就被同时启动,并且始终伴随整个Spark应用的生命周期。
两大作用:
1.负责运行组成Spark应用的任务,并将结果返回给驱动程序
2.他们通过自身的块管理器(Block Manager)为用户程序中要求缓存的RDD提供内存式存储。RDD是直接缓存在执行器进程内的,因此任务可以在运行时充分利用缓存数据加速运算。
几个问题
1、SparkContext是在哪一端生成的?
Driver
2、RDD是在哪一端生成的?
Driver
3、调用RDD的算子(Transformation和Action)是在哪一端调用的
Driver
4、RDD在调用Transformation和Action时需要传入一个函数,函数是在哪一端声明和传入的?
Driver
5、RDD在调用Transformation和Action时需要传入函数,请问传入的函数是在哪一端执行了函数的业务逻辑?
Executor
6、自定义的分区器这个类是在哪一端实例化的?
Driver
7、分区器中的getParitition(获取分区号)方法在哪一端调用的呢?
Executor
8、DAG是在哪一端被构建的?
Driver
9、DAG是在哪一端构建好的并被划分为一到多个Stage的
Driver
10、DAG是哪个类完成的切分Stage的功能?
Driver
11、DAGScheduler将切分好的Stage以什么样的形式给TaskScheduler?
Driver
12、Task是在哪一端生成的呢?
Driver
13、广播变量是在哪一端调用的方法进行广播的?
Driver
14、要广播的数据应该在哪一端先创建好再广播呢?
Driver
stage的划分过程
stage 是一个 job 的组成单位,就是说,一个 job 会被切分成 1 个或 1 个以上的 stage,然后各个 stage 会按照执行顺序依次执行。