本文主要介绍spark中常用的算子的作用记忆部分示例总结。
从总体上来分,spark算子可以分为两大类:transformation和action,其中transformation触发会记录元数据信息,延迟执行,只有触发action才会真正的执行计算。
从小方向上来讲,spark算子可以分为三种类型:value类型transformation算子,key-value类型transformation算子以及action算子。what is RDD?
Internally, each RDD is characterized by five main properties:
接下来介绍常用的一些spark算子。
transformation算子
value类型transformation算子
map算子
map算子将原来RDD中的每一个数据通过map中的用户自定义方法f映射转变成新的元素。1
2
3
4
5
6
7
8scala> val rdd1 = sc.parallelize(List(1, 2, 3))
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at :24
scala> val rdd2 = rdd1.map(_*10)
rdd2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[1] at map at :26
scala> rdd2.collect()
res0: Array[Int] = Array(10, 20, 30)
flatMap算子
flatMap将RDD中的每一个元素通过函数f转成新的元素,并将RDD的每个集合中的元素合并成一个集合。1
2
3
4
5scala> val rdd1 = sc.parallelize(Array("a b c", "d e f", "h i j"))
rdd1: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[8] at parallelize at :24
scala> val rdd2 = rdd1.flatMap(_.split(' ')).collect
rdd2: Array[String] = Array(a, b, c, d, e, f, h, i, j)
mapPartitions算子
与map类似,但是在RDD的每个分区(块)上分别运行,所以当在T类型的RDD上运行时,func的类型必须是Iterator=> Iterator 。
glom 算子
glom将每个分区形成一个数组,内部实现返回ClommedRDD
union算子
使用union函数要保证两个RDD元素的数据类型相同,返回的RDD类型与被合并的RDD数据类型相同,并不进行去重操作,保存所有元素。
如果去重,可以使用distinct()进行处理。
cartesian算子
对两个RDD的所有元素进行笛卡尔积操作,返回CartesianRDD.1
2
3
4
5
6
7
8
9
10
11scala> val rdd1 = sc.parallelize(List("tom", "jerry"))
rdd1: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[21] at parallelize at :24
scala> val rdd2 = sc.parallelize(List("tom", "kitty", "shuke"))
rdd2: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[22] at parallelize at :24
scala> val rdd3 = rdd1.cartesian(rdd2)
rdd3: org.apache.spark.rdd.RDD[(String, String)] = CartesianRDD[23] at cartesian at :28
scala> rdd3.collect
res3: Array[(String, String)] = Array((tom,tom), (tom,kitty), (tom,shuke), (jerry,tom), (jerry,kitty), (jerry,shuke))
groupBy算子
将元素通过函数生成相应的key,数据转换为key-value形式,将key相同的元素分为一组。1
2
3
4
5
6
7
8scala> rdd3.collect
res1: Array[(String, (Int, Int))] = Array((tom,(1,8)), (jerry,(2,9)))
scala> val rdd4 = rdd3.groupByKey
rdd4: org.apache.spark.rdd.RDD[(String, Iterable[(Int, Int)])] = MapPartitionsRDD[20] at groupByKey at :30
scala> rdd4.collect
res2: Array[(String, Iterable[(Int, Int)])] = Array((tom,CompactBuffer((1,8))), (jerry,CompactBuffer((2,9))))
filter算子
对RDD中的元素按照函数f进行过滤。1
2
3
4
5scala> val a = sc.parallelize(1 to 9, 2)
a: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[11] at parallelize at :24
scala> val rdd = a.filter(_ <5).collect
rdd: Array[Int] = Array(1, 2, 3, 4)
distinct算子
对RDD中的元素进行去重操作。
subtract算子
对两个RDD进行集合的差操作。
sample算子
sample 将 RDD 这个集合内的元素进行采样,获取所有元素的子集。用户可以设定是否有放回的抽样、百分比、随机种子,进而决定采样方式。内部实现是生成 SampledRDD(withReplacement, fraction, seed)。
takeSample算子
类似sample算子,通过设定采样个数进行采样,返回结果的集合为单机数组。
cache算子
将RDD元素缓存到内存中
persist算子
persist 函数对 RDD 进行缓存操作。
mapValues算子
针对(key, value)类型中的value进行map操作。
combineByKey算子
按照键值key进行聚合。1
2
3
4
5
6
7
8val initialScores = Array(("Fred", 88.0), ("Fred", 95.0), ("Fred", 91.0), ("Wilma", 93.0), ("Wilma", 95.0), ("Wilma", 98.0))
val d1 = sc.parallelize(initialScores)
type MVType = (Int, Double) //定义一个元组类型(科目计数器,分数)
d1.combineByKey(
score => (1, score),
(c1: MVType, newScore) => (c1._1 + 1, c1._2 + newScore),
(c1: MVType, c2: MVType) => (c1._1 + c2._1, c1._2 + c2._2)
).map { case (name, (num, socre)) => (name, socre / num) }.collect
reduceByKey算子
combineByKey的简单版,将两个值按照key进行合并。
partitionBy算子
将RDD进行分区操作。
Cogroup算子
join算子
join算子对两个需要连接的RDD进行cogroup操作,将相同key的数据放在一个分区,之后对新的RDD的每一个key元素进行笛卡尔积操作,最后构成一个新的集合。返回值为RDD[(K, (V, W))]
1
2
3
4
5
6
7
8scala> val rdd1 = sc.parallelize(List(("tom", 1), ("jerry", 2), ("kitty", 3)))
rdd1: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[0] at parallelize at :24
scala> val rdd2 = sc.parallelize(List(("jerry", 9), ("tom", 8), ("shuke", 7)))
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[1] at parallelize at :24
scala> val rdd3 = rdd1.join(rdd2).collect
rdd3: Array[(String, (Int, Int))] = Array((tom,(1,8)), (jerry,(2,9)))
leftOutJoin和rightOutJoin算子
LeftOutJoin(左外连接)和RightOutJoin(右外连接)相当于在join的基础上先判断一侧的RDD元素是否为空,如果为空,则填充为空。 如果不为空,则将数据进行连接运算,并返回结果。1
2
3
4
5scala> val rdd4 = rdd1.leftOuterJoin(rdd2).collect
rdd4: Array[(String, (Int, Option[Int]))] = Array((tom,(1,Some(8))), (jerry,(2,Some(9))), (kitty,(3,None)))
scala> val rdd5 = rdd1.rightOuterJoin(rdd2).collect
rdd5: Array[(String, (Option[Int], Int))] = Array((tom,(Some(1),8)), (jerry,(Some(2),9)), (shuke,(None,7)))
action类型算子
foreach算子
foreach对RDD中的每个元素都应用函数f,返回UNIT
saveAsTextFile算子
将数据输出存储在HDFS指定目录中。
saveAsObjectFile算子
将数据输出以sequencefile的格式存储在HDFS目录中。
collect算子
collect将分布式的RDD返回一个ARRAY类型数组。
collectAsMap算子
对(k, v)类型RDD数据返回一个单机hashMap,对于重复的RDD元素,后面的元素覆盖前面的元素。1
2
3
4
5scala> var rdd1 = sc.parallelize(List(("A", 1), ("B", 2), ("C", 3)))
rdd1: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[28] at parallelize at :24
scala> val rdd2 = rdd1.collectAsMap()
rdd2: scala.collection.Map[String,Int] = Map(A -> 1, C -> 3, B -> 2)
reduceByKeyLocally算子
实现的是先reduce再collectAsMap的功能,先对RDD的整体进行reduce操作,然后再收集所有结果返回为一个HashMap。
loopup算子
作用于K-V类型的RDD上,返回指定K的所有V值。1
2scala> val rdd2 = rdd1.lookup("A")
rdd2: Seq[Int] = WrappedArray(1)
count算子
返回RDD的元素个数。
top算子
返回最大的K个元素。
reduce算子
对RDD中的元素进行reduceLeft函数的操作。分别对每个分区进行聚合。1
2
3
4
5scala> val a = sc.parallelize(1 to 100, 3)
a: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at :24
scala> a.reduce(_ + _)
res0: Int = 5050
fold算子
聚合每个分区的值,每个分区内的聚合变量用zeroValue初始化。
def fold(zeroValue: T)(op: (T, T) => T): T1
2
3
4
5
6
7
8
9
10
11scala> val a = sc.parallelize(1 to 100, 3)
a: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at :24
scala> a.fold(0)(_ + _)
res2: Int = 5050
scala> a.fold(1)(_ + _)
res3: Int = 5054
scala> a.fold(2)(_ + _)
res4: Int = 5058
aggregate算子
aggregate先对每个分区的所有元素进行aggregate操作,再对分区的结果进行fold操作。
aggreagate与fold和reduce的不同之处在于,aggregate相当于采用归并的方式进行数据聚集,这种聚集是并行化的。 而在fold和reduce函数的运算过程中,每个分区中需要进行串行处理,每个分区串行计算完结果,结果再按之前的方式进行聚集,并返回最终聚集结果。1
2scala> a.aggregate(0)(_+_, _+_)
res8: Int = 5050
根据Zhen He的解释,聚合功能允许用户将两个不同的函数应用在RDD中,在每个分区中应用第一个reduce函数,将每个分区内的数据减少为单个结果。第二个reduce函数函数将用于将所有分区的结果聚合在一起,以得到最终结果。