一、什么是 RDD(从源码解读)
1、A Resilient Distributed Dataset (RDD), the basic abstraction in Spark.
RDD 弹性分布式数据集,是 Spark 中的最基本抽象。
✓ Resilient ==> 指的是RDD的分区(代表着RDD数据分为几份)数量是可以进行弹性控制的;
✓ Distributed ==> 指的是RDD的分区分布式的存在于各个执行(Executor)节点上/task运行是分布式的;
✓ Dataset ==> RDD中描述的是一个数据集,类似于scala语法中 ArrayBuffer 或 ListBuffer 大的数据集合,不过是分布式的。
2、Represents an immutable, partitioned collection of elements that can be operated on in parallel.
代表着一个不可变的,可以分区的(切片)的,元素可以被并行化的操作分布式的集合。
✓ 不可变指的是从语法角度讲相当于定长 Array 和不可变的 List,所有的并行化操作(修改和更新)都将产生新的 Array 和 List,也就 和 MR 一样倾向于创建新数据,而不是对原数据进行修改。
二、RDD 的五大特性
*Internally,each RDD is characterized by five main properties:
从内部来讲每个RDD都基本以下五个属性(特性):
*-A list of partitions
1) 每个RDD都有一系列的分区组成,相当于HDFS的block,或者是MapReduce读取的切片
*-A function for computing each split
2) 每个分片或分区上都使用一个共同的函数来进行计算/执行/迭代,e.g. val pairs = words.map(word=>(word,1))
*-A list of dependencies on other RDDs
3) 一系列的依赖:除了刚创建的的RDD(textFile读取进来)之外,其他的RDD都有依赖,即 words =》pairs,那么pairs就依赖于words
*-Optionally,a Partitioner for key-value RDDs(e.g.to say that the RDD is hash-partitioned)
4) 可选的,对于key-value的这种pair类型的RDD,用户可以根据需要自己指定分区方式,比如 hash range
*-Optionally,a list of preferred locations to compute each split on(e.g.blocklocationsfor an HDFS file)
5) 可选的,每个切片在执行的时候, spark 都会为其寻找最优执行位置,已达到最理想的运行速度,类似于 mr 读取切片时候的就近原则
三、RDD 的创建方式
方式一:并行化在Driver端的已存在的数据集
举例,如下:
val data = Seq(1,2,3,4,5)
val rdd = sc.parallelize(data1)
----------------------------------------------------------------------------------
scala> val a = Array(1,2,3,4,5)
a: Array[Int] = Array(1, 2, 3, 4, 5)
scala> val rdd0 = sc.parallelize(a)
rdd0: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[10] at parallelize at <console>:26
----------------------------------------------------------------------------------
scala> val b = List(1,2,3,4,5)
b: List[Int] = List(1, 2, 3, 4, 5)
scala> val rdd1 = sc.parallelize(b)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[11] at parallelize at <console>:26
-----------------------------------------------------------------------------------
scala> val c = Seq(1,2,3,4,5)
c: Seq[Int] = List(1, 2, 3, 4, 5)
scala> val rdd2 = sc.parallelize(c)
rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[12] at parallelize at <console>:26
-----------------------------------------------------------------------------------
scala> val d = scala.collection.mutable.ArrayBuffer(1,2,3,4,5)
d: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5)
scala> val rdd3 = sc.parallelize(d)
rdd3: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[13] at parallelize at <console>:26
-----------------------------------------------------------------------------------
scala> val rdd4 = sc.parallelize(1 to 10)
rdd4: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[14] at parallelize at <console>:24
-----------------------------------------------------------------------------------
默认情况下无论数据集大小,生成的分区数由 math.min(defaultParallelism,2) 决定,defaultParallelism=运行当前程序的分配的CPU的核
注意:
如果数据集比较大,SparkContext.parallelize(seq: Seq[T], numSlices: Int = defaultParallelism=2),注意调整 numSlices 增加并行度
方式二:引用外部数据源(分布式文件系统:HDFS,HBase,任何提供了 Hadoop 的 InputFromt 接口)
InputFormat
-1. FileInputFormat(text)
-2. DBInputForamt(关系型数据库)
-3. TableInputFormat(HBase)
val rdd = sc.textFile("/input/wc.txt")
textFile(path: String, minPartitions: Int = defaultMinPartitions ) ,默认的最小分区数是 2
hadoopFile(path,
classOf[TextInputFormat],
classOf[LongWritable],
classOf[Text],minPartitions)
.map(pair => pair._2.toString).setName(path)=>/input/wc.txt[(每一行的偏移量,每一行的内容)].map()
rdd.repartition(4)
拓展阅读:
[转自 “ _和 ”] spark将数据写入HBase以及从HBase读取数据
https://blog.csdn.net/u011812294/article/details/72553150
方式三:调用自身Transformation算子处理已存在的RDD,生成新的子RDD
比如:
map() | flatMap() | filter() | mapPartitions() | mapPartitionsWithIndex() |
sample() | union() | intersection() | distinct() | groupByKey() |
reduceByKey() | sortByKey() | join() | cogroup | cartesion() |
pipe() | coalesce() | repartition() | partitionBy() | ...... |
四、RDD Operations 算子(或操作)的类型
官方定义:RDDs support two types of operations:transformations, which create a new dataset from an existing one,and actions, which return a value to the driver program after running a computation on the dataset。
1、transformations(转换)
从已有的rdd来创建,返回值是一个新的RDD,数据依然在Executor
特点:所有的transformations算子都是懒加载,不会立即触发计算,不会形成job。
2、actions(行为/动)
对RDD使用compute的function,对算据集进行计算,并将结果返回给Driver端,或者保存到外部存储系统,返回值不是新RDD,这类操作会形成job。
比如:count、reduce、collect、take……
在实际开发中,慎重使用最好不使用collect,因为会拉取所有数据,瞬间拉爆spark,使用 take() 代替。
从网上有看到,有些人将 persist/cache 算子(持久化)算作一种类型。但实际上,缓存 persist/cache 是懒加载的,应该算作 transformations 类型算子,而 unpersist/uncache 是紧急的,算作 actions 类型算子。
说到这里,简单比较一下 persist/cache 的区别:
a. 相同之处,都可以将 RDD 的数据在 Executor 内存中进行缓存,当一个 RDD 的数据会被多个后续的 job 使用的时候,可以进行进行缓存,好处在于在后续 job 执行的时候不需要从头进行计算,当后续的某个数据丢失的时候也不需要从头计算。;
b. 不同之处cache的缓存级别只有 MEMORY_ONLY(只有内存),而 persist 有 12 个级别的缓存(可以查看spark源码),其实cache 就是 persist(MEMORY_ONLY);
c. cache 不需要用户手动释放,而后者需要调用 unpersist;
d. persist/cache 是懒加载,而释放缓存是紧急的,可以通过 4040 监控页面 Storage 查看到。