RDD中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果。相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。这种设计让Spark更加有效率地运行。
也就是说Transformation算子,spark程序没有计算,遇到action算子开始计算
Transformation
单value结构 | ||
map(func) | 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成 | rdd.map((_,1)) |
filter(func) | 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成 | x.filter(x % 2 == 0) |
flatMap(func) | 类似于map,但是每一个输入元素可以被映射为0或多个输出元素 | rdd.flatMap(_.split(" ")) |
mapPartitions(func) | 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U] | rdd.mapPartitions(x => x.map((_,1))) |
mapPartitionsWithIndex(func) | 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U] | rdd.mapPartitionsWithIndex((index,items)=>(items.map((index,_)))) |
sample(withReplacement, fraction, seed) | 根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子 | |
glom | 将每一个分区形成一个数组,形成新的RDD[Array[T]] | val value: RDD[Array[Int]] = rdd.glom() |
groupBy | 按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器 | val groupBy: RDD[(Int, Iterable[Int])] = rdd.groupBy(_%3) |
distinct([numTasks])) | 对源RDD进行去重后返回一个新的RDD | rdd.distinct() |
coalesce(numPartitions) | 重新分区 | rdd.coalesce(3) |
repartition(numPartitions) | 重新分区 | rdd.repartition(2) |
sortBy | rdd.sortBy(x => x%3) | |
pipe | 管道,针对每个分区,都执行一个shell脚本,返回输出的RDD,注意:脚本需要放在Worker节点可以访问到的位置 | rdd.pipe("/opt/module/spark/pipe.sh") |
双value结构 | ||
union(otherDataset) | 两个rdd的并集 | rdd1.union(rdd2) |
intersection(otherDataset) | 两个rdd的交集 | rdd1.intersection(rdd2) |
subtract | 两个rdd的差集 | rdd.subtract(rdd1) |
zip | 将两个RDD组合成Key/Value形式的RDD,这但是两个RDD的partition数量以及元素数量都相同,否则会抛出异常 | |
cartesian | 笛卡尔积,尽量避免使用 | rdd1.cartesian(rdd2). |
val rdd1 = sc.parallelize(Array(1,2,3),3)
val rdd2 = sc.parallelize(Array("a","b","c"),3)
rdd1.zip(rdd2).collect
res1: Array[(Int, String)] = Array((1,a), (2,b), (3,c))
//foreachPartition一般对分区的一个元素和items操作,一般用来数据库操作
key2CountActionRDD.foreachPartition{
items =>
val statArray = new ArrayBuffer[AdStat]()
for (item <- items) {
val keySplited: Array[String] = item._1.split("_")
val date = keySplited(0)
val province = keySplited(1)
val city = keySplited(2)
val adid = keySplited(3).toLong
val clickCount = item._2
statArray += AdStat(date, province, city, adid, clickCount)
}
AdStatDAO.updateBatch(statArray.toArray)
}
k-v结构 | ||
partitionBy | 按照k进行分区 | rdd.partitionBy(new org.apache.spark.HashPartitioner(2)) |
groupByKey([numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD | val groupByKey: RDD[(Int, Iterable[Int])] = kvRdd.groupByKey() |
sortByKey([ascending], [numTasks]) | 只能按照k排序,K必须实现Ordered接口 | rdd.sortByKey(true) |
mapValues | 针对于(K,V)形式的类型只对V进行操作 | rdd3.mapValues("@"+_) //对每个value前面都加上字符串@ |
join | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD | |
cogroup | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD | |
reduceByKey(func, [numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置 | val reduceByKey = kvrdd.reduceByKey(+) |
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) | ||
foldByKey | ||
combineByKey |
join
> val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
> val rdd1 = sc.parallelize(Array((1,4),(2,5),(3,6)))
> rdd.join(rdd1).collect()
res13: Array[(Int, (String, Int))] = Array((1,(a,4)), (2,(b,5)), (3,(c,6)))
cogroup
val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
val rdd1 = sc.parallelize(Array((1,4),(2,5),(3,6)))
rdd.cogroup(rdd1).collect()
res14: Array[(Int, (Iterable[String], Iterable[Int]))] = Array((1,(CompactBuffer(a),CompactBuffer(4))), (2,(CompactBuffer(b),CompactBuffer(5))), (3,(CompactBuffer(c),CompactBuffer(6))))
aggregateByKey
参数:(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
- 作用:在kv对的RDD中,,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),将key与计算结果作为一个新的kv对输出。
- 参数描述:
(1)zeroValue:给每一个分区中的每一个key一个初始值;
(2)seqOp:函数用于在每一个分区中用初始值逐步迭代value;
(3)combOp:函数用于合并每个分区中的结果。 - 经典需求:创建一个pairRDD,取出每个分区相同key对应值的最大值,然后相加
> val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)
> val agg = rdd.aggregateByKey(0)(math.max(_,_),_+_)
> agg.collect()
res0: Array[(String, Int)] = Array((b,3), (a,3), (c,12))
foldByKey
参数:(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
- 作用:aggregateByKey的简化操作,seqop和combop相同
- 需求:创建一个pairRDD,计算相同key对应值的相加结果
> val rdd = sc.parallelize(List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)),3)
> val agg = rdd.foldByKey(0)(_+_)
> agg.collect()
res61: Array[(Int, Int)] = Array((3,14), (1,9), (2,3))
combineByKey
参数:(createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)
- 作用:对相同K,把V合并成一个集合。
- 参数描述:
(1)createCombiner: combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值
(2)mergeValue: 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并
(3)mergeCombiners: 由于每个分区都是独立处理的, 因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 就需要使用用户提供的 mergeCombiners() 方法将各个分区的结果进行合并。 - 经典需求:创建一个pairRDD,根据key计算每种key的均值。(先计算每个key出现的次数以及可以对应值的总和,再相除得到结果)
val input = sc.sparkContext.parallelize(Array(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)), 2)
val combine: RDD[(String, (Int, Int))] = input.combineByKey(
(_, 1),
(acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),
(acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2))
println(combine.collect().mkString(""))//(b,(286,3))(a,(274,3))
val res: RDD[(String, Double)] = combine.map {
case (key, value) =>
(key, value._1 / value._2.toDouble)
}
println(res.collect().mkString(""))//(b,95.33333333333333)(a,91.33333333333333)
Action
算子 | 介绍 | |
reduce(func) | 通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的 | |
collect() | 在驱动程序中,以数组的形式返回数据集的所有元素 | |
count() | 返回RDD的元素个数 | rdd.count |
first() | 返回RDD的第一个元素(类似于take(1)) | |
take(n) | 返回一个由数据集的前n个元素组成的数组 | |
takeSample(withReplacement,num, [seed]) | 返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子 | |
takeOrdered(n,?[ordering]) | takeOrdered和top类似,只不过以和top相反的顺序返回元素 | |
saveAsTextFile(path) | 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本 | |
saveAsSequenceFile(path) | 将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。 | |
saveAsObjectFile(path) | ||
countByKey() | 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。 | rdd.countByKey |
foreach(func) | 在数据集的每一个元素上,运行函数func进行更新。 |
map与flatMap的区别以及mapPartiton和flatMapPartition 以及foreach和foreachPartition
map是一对一操作,输入一个元素,就输出一个元素,返回新的RDD
flatMap是输入一个元素,但是输出可以使0个或者多个元素,返回新的RDD
foreach也会遍历每一个元素,但是他是没有返回值的,不会生成RDD,而且它是一个action算子
他们都是应用于每一个元素也就是每条数据执行一次,task为每个数据,都要去执行一次function函数。
而partition算子一个task仅仅会执行一次function,function一次接收所有的partition数据。只要执行一次就可以了,性能比较高,但如果数据量过大的话会导致一个分区的数据放到内存中执行导致oom
适用于分析的数据量不是特别大的时候,或者估算一下RDD的数据量,以及每个partition的量,还有自己分配给每个executor的内存资源。看看一下子内存容纳所有的partition数据行不行
reduce与groupby的区别
reduceByKey在shuffle之前进行分区内分组聚合,在到reduce进行分区间分组聚合,减少reduce的压力
groupByKey所有的kv都会移动