Spark的Transform算子和Action算子列举和示例
一:Spark的算子的分类
1. 从大方向来说,Spark 算子大致可以分为以下两类:
1)TransFormation:变换/转换算子(懒加载):不触发提交作业,完成作业中间过程处理。
TransFormation操作是延迟计算的,也就是说从一个RDD转换生成另一个RDD的转换操作
不是马上执行,需要等到有 Action 操作时才会真正触发运算。
2)Action行动算子:这类算子会触发 SparkContext 提交job作业。
Action算子会触发Spark提交作业(Job),并将数据输出Spark系统。
从小方向来说,Spark算子大致分以下三类:
1)Value数据类型的Transformation算子,这种变换并不触发提交作业,针对处理的数据项是Value型的数据。
2)Key-Value数据类型的Transformation算子,这种变换并不触发提交作业,针对处理的数据项是Key-Value型的数据对。
3)Action算子,这类算子会触发SparkContext提交Job作业。
1)Value 数据类型的Transformation算子
一丶输入分区与输出分区一对一
1、map算子
2、flatMap算子
3、mapParitions算子
4、glom算子
二、输入分区与输出分区多对一
5、union算子
6、cartesian算子
三、输入分区与输出分区多对多
7、groupBy算子
四、输出分区为输入分区子集
8、filter算子
9、distinct算子
10、subtract算子
11、sample算子
12、takeSample算子
五、Cache
13、cache算子
14、persist算子
2)key-Value数据类型的Transformation算子
一、输入分区与输出分区一对一
15、mapValues算子
二、对单个RDD或两个RDD聚集
单个RDD聚集:
16、combinerByKey算子
17、reduceByKey算子
18、partitionBy算子
两个RDD聚集
19、Cogroup算子
三、连接
20、join算子
21、leftOutJoin和rightOutJoin算子
3)Action算子
一:无输出
22、foreach算子
二:HDFS
23、saveAsTextFile算子
24、saveAsObject算子
三:Scala集合和数据类型
25、collect算子
26、collectAsMap算子
27、reduceByKeyLocally算子
28、lookup算子
29、count算子
30、top算子
31、reduce算子
32、fold算子
33、aggregate算子
1.Transformations 算子
--map
将原来RDD的每个数据项通过map中的用户自定义函数f映射转变为一个新的函数.
def map(sc: SparkContext) = {
val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5), (3))
println(rdd.map(x => x * 100).collect().toBuffer)
//Res:ArrayBuffer(100, 200, 300, 400, 500)
}
--flatMap
将原来RDD中的每个元素通过函数转换为新的元素,并将生成的RDD的每个集合中的元素合并为一个集合。
def flatMap(sc: SparkContext) = {
val rdd: RDD[String] = sc.parallelize(Array("ab","cd","ef"))
println( rdd.flatMap(x => x+"@").collect().toBuffer)
rdd.flatMap(x=>x+"#").foreach(print(_))
//res:ArrayBuffer(a, b, @, c, d, @, e, f, @)
//res:ab#cd#ef#
}
--mapPartitions
mapPartitions函数获取到每个分区的迭代器,在函数中通过这个分区整体的迭代器对整个分区的元素进行操作。
效果和map一样,只是操作对象是一个迭代器。对分区的操作。map是对每个元素的操作。
def mapPartitions(sc: SparkContext) = {
val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6), (3))
//rdd.mapPartitions(it => it.filter(_>3)).collect().toBuffer
println(rdd.mapPartitions(it => it.filter(_ > 3)).collect().toBuffer)
rdd.mapPartitions(it => it.filter(_ < 8)).foreach(print(_))
//Res:314256
//Res: ArrayBuffer(4, 5, 6)
}
--glom
glom函数将每个分区形成一个数组,内部实现是返回的GlommedRDD.
def glom(sc: SparkContext) = {
val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6), 2)
println(rdd.glom().collect.toBuffer)
//Res:ArrayBuffer([I@766a4535, [I@63718b93)
}
--union
使用union函数时需要保证两个RDD元素的类型相同,返回的RDD数据类型和合并的RDD元素数据类型相同,
并不进行去重操作,保存所有元素。
def union(sc: SparkContext) = {
val rdd: RDD[Int] = sc.parallelize(Array(1,2,3,4),3)
val unionRdd: RDD[Int] = rdd.union(rdd).union(rdd)
//输出分区数
println(unionRdd.partitions.size)
// unionRdd.foreach(print(_))
//Res:9
}
--cartesian
对两个RDD内的所有元素进行笛卡尔积操作。操作后,内部实现返回CartesianRDD
def cartesian(sc: SparkContext) = {
val rdd: RDD[Int] = sc.parallelize(Array(1,2))
println(rdd.cartesian(rdd).partitions.size)
println(rdd.cartesian(rdd).collect().toBuffer)
//res:4
//res:ArrayBuffer((1,1), (1,2), (2,1), (2,2))
}
--groupBy
将元素通过函数生成相应的key,数据就转化为key-Value格式,之后将key相同的元素分为一组。
def groupBy(sc: SparkContext): Unit = {
val rdd = sc.parallelize(1 to 10, 3)
println(
rdd.groupBy(x => {
if (x % 2 == 0) " even" else "odd"
}).collect.toBuffer)
//Res:ArrayBuffer(( even,CompactBuffer(2, 4, 6, 8, 10)), (odd,CompactBuffer(1, 3, 5, 7, 9)))
}
--filter
filter函数功能是对元素进行过滤,对每个元素应用函数,返回值为true的元素在RDD中
保留,返回值为false的元素进行过滤
def filter(sc: SparkContext) = {
val rdd: RDD[Int] = sc.parallelize(Array(1,2,3,4),3)
println(rdd.filter(_>=3).collect().toBuffer)
//Res:ArrayBuffer(3, 4)
}
--distinct
distinct 将RDD中的元素进行去重操作。
def distinct(sc: SparkContext) = {
val rdd: RDD[Int] = sc.parallelize(Array(1,2,3,3,2,1,1,0,2,4,2,3),3)
println(rdd.distinct().collect().toBuffer)
//Res: ArrayBuffer(0, 3, 4, 1, 2)
}
--subtract
subtract相当于进行集合的差操作,RDD 1 去除RDD 2 交集中的所有元素。
def subtract(sc: SparkContext) = {
val rdd1: RDD[Int] = sc.parallelize(1 to 9, 3)
val rdd2: RDD[Int] = sc.parallelize(1 to 3, 3)
println(rdd1.subtract(rdd2).collect().toBuffer)
//Res:ArrayBuffer(6, 9, 4, 7, 5, 8)
}
--sample
sample将RDD这个集合内的元素进行采样,获取所有元素子集。
用户可以设定是否有放回的抽样、百分比、随机种子,进而决定采样方式。
内部实现是生成 SampledRDD(withReplacement, fraction, seed).
函数参数设置:
withReplacement=true,表示有放回的抽样。
withReplacement=false,表示无放回的抽样
Fraction=>,一个大于0,小于或等于1的小数值,用于控制要读取的数据所占整个数据集的概率。
Seed=>,这个值如果没有传入,默认值是一个0~Long.maxvalue之间的整数。
def sample(sc: SparkContext) = {
val rdd: RDD[Int] = sc.parallelize(1 to 10000,3)
println(rdd.sample(false,0.1,0))
println(rdd.sample(false,0.1,0).collect().toBuffer)
//Res:PartitionwiseSampledRDD[1] at sample at DemoSZ.scala:88
//Res:................................
}
--takeSample
takeSample()函数和上面的sample函数是一个原理,但是不使用相对比例采样,
而是按设定的采样个数进行采样,同时返回结果不再是RDD,而是相当于对采样后的数据进行Collect()
--cache
cache 将 RDD 元素从磁盘缓存到内存。 相当于 persist(MEMORY_ONLY) 函数的功能
--persist
persist 函数对 RDD 进行缓存操作。数据缓存在哪里依据 StorageLevel 这个枚举类型进行确定。
有以下几种类型的组合, DISK 代表磁盘,MEMORY 代表内存, SER 代表数据是否进行序列化存储。
下面为函数定义, StorageLevel 是枚举类型,代表存储模式,可以通过按需进行选择。
persist(newLevel:StorageLevel).图 中列出persist 函数可以进行缓存的模式。例如,
MEMORY_AND_DISK_SER 代表数据可以存储在内存和磁盘,并且以序列化的方式存储,其他同理。
def persist(sc: SparkContext) = {
val rdd: RDD[Int] = sc.parallelize(1 to 100, 3)
//println(rdd.persist(StorageLevel.MEMORY_AND_DISK))
// println(rdd.persist(StorageLevel.MEMORY_AND_DISK).collect().toBuffer)
//Res:......................
}
----repartition和coalesce
repartition(numPartitions:Int):RDD[T]
coalesce(numPartitions:Int,shuffle:Boolean=false):RDD[T]
他们两个都是RDD的分区进行重新划分,repartition只是coalesce接口中shuffle为true的简易实现,
(假设RDD有N个分区,需要重新划分成M个分区)
1.如果 N小于M 一般情况下N个分区有数据分布不均匀的状况,利用HashPartitioner函数将数据重新分区为M个,
这时需要将shuffle设置为true。
2.如果N>M并且N和M相差不多,(假如N是1000,M是100)那么就可以将N个分区中的若干个分区合并成一个新的分区,
最终合并为M个分区,这时可以将shuff设置为false,在shuffl为false的情况下,如果M>N时,coalesce为无效的,
不进行shuffle过程,父RDD和子RDD之间是窄依赖关系。
3.如果N>M并且两者相差悬殊,这时如果将shuffle设置为false,父子RDD是窄依赖关系,
他们同处在一个Stage中,就可能造成Spark程序的并行度不够,从而影响性能,如果在M为1的时候,
为了使coalesce之前的操作有更好的并行度,可以讲shuffle设置为true。
总之:如果shuff为false时,如果传入的参数大于现有的分区数目,RDD的分区数不变,
也就是说不经过shuffle,是无法将RDDde分区数变多的。
Transform里面的Key-Value形式的RDD
--mapValues
mapValues是针对(key,value)型数据中的Value进行Map操作,而不对Key进行处理
def mapValues(sc: SparkContext) = {
val rdd: RDD[(String, Int)] = sc.parallelize(Array(("a",1),("b",2),("c",3)),3)
//rdd.mapValues{_*10}.foreach(println(_))
rdd.mapValues{_*10}.foreach(println)
//Res:(a,10) (b,20) (c,30)
}
--combineByKey
combineByKey[C](
createCombiner:(V) C,
mergeValue:(C, V) C,
mergeCombiners:(C, C) C,
partitioner:Partitioner,
mapSideCombine:Boolean=true,
serializer:Serializer=null):RDD[(K,C)]
说明:数据为RDD[K,V]
createCombiner:是将V值类型转为C类型.
mergeValue:将C和V进行操作(理解为单分区里面的操作)
mergeCombiners:将C和C进行操作//理解为分区与分区直接的操作
partitioner:分区方式
mapSideCombine:是否在map端先进行操作
serializer:序列号方式
def combineByKey(sc: SparkContext) = {
val rdd: RDD[(Int, Int)] = sc.parallelize(Array((0,5),(0,4),(2,3),(2,2),(0,1)),3)
val createCombiner=(a:Int)=>List(a)//将5转换成List(5)
val mergeValue=(a:List[Int],b:Int)=>a.::(b)//将List(5)和4操作成List(5,4)
val mergeCombiners=(a:List[Int],b:List[Int])=>a.:::(b)//将List(4,5)和List(1)操作得到List(1,4,5)
println(rdd.combineByKey(createCombiner,mergeValue,mergeCombiners).collect().toBuffer)
//Res:ArrayBuffer((0,List(1, 4, 5)), (2,List(2, 3)))
}
--reduceByKey
做WordCount的时候用到,按照可以来做一个聚合操作。底层是
combineByKey。只是那个C跟V是一个类型。
API:reduceByKey(partitioner: Partitioner, func: (V, V) => V)
def reduceByKey(sc: SparkContext) = {
val rdd: RDD[(Int, Int)] = sc.parallelize(Array((0, 5), (0, 4), (2, 3), (2, 2), (0, 1)), 3)
println(rdd.reduceByKey((a, b) => a + b).collect.toBuffer)
//Res:ArrayBuffer((0,10), (2,5))
}
--partitionBy
重分区partitionBy(Partitioner)
def partitionBy(sc: SparkContext) = {
val rdd: RDD[(Int, Int)] = sc.parallelize(Array((0,5),(0,4),(2,3),(2,2),(0,1)), 3)
val redRdd: RDD[(Int, Int)] = rdd.partitionBy(new HashPartitioner(10))
//redRdd: RDD[(Int, Int)] = rdd.partitionBy(new HashPartitioner(10)) //重新分为10个区
//println(rdd.partitionBy(new HashPartitioner(10)).collect.toBuffer)
println(redRdd.getNumPartitions)//10
}
--Cogroup
返回两个Rdd相同key的各自数据集合
cogroup[W](other: RDD[(K, W)],
numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W]))]
def cogroup(sc: SparkContext) = {
val rdd: RDD[(Int, Int)] = sc.parallelize(Array((0, 5), (0, 4), (2, 3), (2, 2), (0, 1)), 3)
rdd.cogroup(rdd).foreach(println) //
//(2,(CompactBuffer(3, 2),CompactBuffer(3, 2)))
//(0,(CompactBuffer(5, 4, 1),CompactBuffer(5, 4, 1)))
}
--join
两个RDD做join操作
RDD[(K,V)].join(other: RDD[(K, W)]): RDD[(K, (V, W))]
如果join不到的就丢弃
def join(sc: SparkContext) = {
val rdd = sc.parallelize(Array((0, 1), (0, 2), (2, 3)), 3)
val rdd2 = sc.parallelize(Array((0, 11), (0, 22), (3, 33)), 3)
rdd.join(rdd2).foreach(println)
//(0,(1,11))
//(0,(1,22))
//(0,(2,11))
//(0,(2,22))
}
--leftOutJoin和rightOutJoin
leftOutJoin:join过程中,如果找不到相同的key。
则还是保留左边RDD中的数据;
RDD[(K,V)].join(other: RDD[(K, W)]): RDD[(K, (V, Option[W])]
Action算子
以Count为例:点进count的源码可以看到,程序提交了一个job
def count(): Long = sc.runJob(this, Utils.getIteratorSize _).sum
--count
返回rdd的元素个数
--foreach
对rdd的每个元素一次做操作,但没有返回值,最常用的就是
foreach(println)
--saveAsTextFile
函数将数据输出,存储到 HDFS 的指定目录。
将 RDD 中的每个元素映射转变为 (null, x.toString),
然后再将其写入 HDFS。图 中左侧方框代表 RDD 分区,
右侧方框代表 HDFS 的 Block。通过函数将RDD 的每个分区存储为
HDFS 中的一个 Block
--saveAsObjectFile
saveAsObjectFile将分区中的每10个元素组成一个Array,然后将这个Array序列化,
映射为(Null,BytesWritable(Y))的元素,写入HDFS为SequenceFile的格式。
--collect
将RDD的数据收集到driver端。数据量大的数据会报OOM.不建议使用,通常用take(num:Int)来代替。
--collectAsMap
collectAsMap对(K,V)型的RDD数据返回一个单机HashMap。
对于重复K的RDD元素,后面的元素覆盖前面的元素。
--lookup
lookup(key:K):Seq[V]
Lookup函数对(Key,Value)型的RDD操作,返回指定Key对应的元素形成的Seq。
这个函数处理优化的部分在于,如果这个RDD包含分区器,则只会对应处理K所在的分区,
然后返回由(K,V)形成的Seq。 如果RDD不包含分区器,则需要对全RDD元素进行暴力扫描处理,
搜索指定K对应的元素。
--top
top(num:Int)(implicit ord:Ordering[T]):Array[T]
//自定义一个排序规则(倒序)
object MyOrdering extends Ordering[Int] {
def compare(a:Int, b:Int) = b compare a
}
top(10)(MyOrdering )
top返回最大的k个元素。
take返回前k个元素。
--reduce
将数据及合成一个元素
--fold
fold和reduce的原理相同,但是与reduce不同,相当于每个reduce时,
迭代器取的第一个元素是zeroValue
val a=sc.parallelize(List(1,2,3),3)
println(a.fold(0)(_+_))
println(a.fold(10)(_+_))
//Res:6
//Res:46
--- aggregate
aggregate先对每个分区的所有元素进行aggregate操作,再对分区的结果进行fold操作。
aggreagate与fold和reduce的不同之处在于,aggregate相当于采用归并的方式进行数据聚集,
这种聚集是并行化的。 而在fold和reduce函数的运算过程中,每个分区中需要进行串行处理,
每个分区串行计算完结果,结果再按之前的方式进行聚集,并返回最终聚集结果。
aggregate[B](z: B)(seqop: (B,A) => B,combop: (B,B) => B): B
val z = sc.parallelize(List(1, 2, 3, 4, 5, 6), 2)
// lets first print out the contents of the RDD with partition labels
def myfunc(index: Int, iter: Iterator[(Int)]): Iterator[String] = {
iter.toList.map(x => "[partID:" + index + ", val: " + x + "]").iterator
}
println(z.mapPartitionsWithIndex(myfunc).collect.toBuffer)
//ArrayBuffer([partID:0, val: 1], [partID:0, val: 2], [partID:0, val: 3],
// [partID:1, val: 4], [partID:1, val: 5], [partID:1, val: 6])
println(z.aggregate(0)(math.max(_, _), _ + _)) //9