键值对RDD(pairRDD)是Spark中许多操作所需要的常见数据类型。
1,创建pairRDD
在Spark中有许多创建pairRDD的方式,很多存储键值对的数据格式会在读取时直接返回由其键值对数据组成的pairRDD。此外当需要把一个普通RDD转换为pairRDD时,可以调用map()函数。简单的创建方式如下:
scala> val lines = sc.parallelize(List("hello world","bi"))
lines: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[4] at parallelize at <console>:29
scala> val pairs = lines.map(x => (x.split(" ")(0),x))
pairs: org.apache.spark.rdd.RDD[(String, String)] = MapPartitionsRDD[5] at map at <console>:30
scala> println(pairs.collect().mkString(","))
(hello,hello world),(bi,bi)
2.常见操作
pairRDD.reduceByKey(func) : 合并具有相同键的值
scala> val pairs = sc.parallelize(List((1,2),(3,4),(3,6)))
pairs: org.apache.spark.rdd.RDD[(Int, Int)] = ParallelCollectionRDD[7] at parallelize at <console>:29
scala> val pair = pairs.reduceByKey((x,y)=>x+y)
pair: org.apache.spark.rdd.RDD[(Int, Int)] = ShuffledRDD[9] at reduceByKey at <console>:30
scala> println(pair.collect().mkString(","))
(1,2),(3,10)
groupBykey() :对具有相同键的值进行分组
scala> val pair = pairs.groupByKey()
pair: org.apache.spark.rdd.RDD[(Int, Iterable[Int])] = ShuffledRDD[10] at groupByKey at <console>:30
scala> println(pair.collect().mkString(","))
(1,CompactBuffer(2)),(3,CompactBuffer(4, 6))
pairRDD.mapValues() :对pairRDD中每个值应用一个函数而不改变键,此时值只能是单个值
scala> val pair = pairs.mapValues(x =>x+1)
pair: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[11] at mapValues at <console>:30
scala> println(pair.collect().mkString(","))
(1,3),(3,5),(3,7)
pairRDD.flatMapValues(func) :对pairRDD中的每个值应用一个返回迭代器的函数,然后对返回的每个元素都生成一个对应原键的键值的键值对记录,通常用于符号化。
scala> val pair = pairs.flatMapValues(x =>(x to 5))
pair: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[12] at flatMapValues at <console>:30
scala> println(pair.collect().mkString(","))
(1,2),(1,3),(1,4),(1,5),(3,4),(3,5)
pairRDD.keys: 返回一个仅包含键的RDD
scala> val pair = pairs.keys
pair: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[13] at keys at <console>:30
scala> println(pair.collect().mkString(","))
pairRDD.values :返回一个仅包含值的RDD
scala> val pair = pairs.values
pair: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[14] at values at <console>:30
scala> println(pair.collect().mkString(","))
2,4,6
pairRDD.sortByKey() :返回一个根据键排序的RDD
scala> val pair = pairs.sortByKey()
pair: org.apache.spark.rdd.RDD[(Int, Int)] = ShuffledRDD[17] at sortByKey at <console>:30
scala> println(pair.collect().mkString(","))
(1,2),(3,4),(3,6)
3.对两个RDD的转化操作(pars = {(1,2),(3,4),(3,6)}) other={(3,9)})
pairRDD.subtractByKey(other) :删掉pairRDD中键与otherRDD中的键相同的元素
scala> val pair = pairs.subtractByKey(other)
pair: org.apache.spark.rdd.RDD[(Int, Int)] = SubtractedRDD[19] at subtractByKey at <console>:32
scala> println(pair.collect().mkString(","))
(1,2)
pairRDD.join(other): 对两个RDD进行内连接
scala> val pair = pairs.join(other)
pair: org.apache.spark.rdd.RDD[(Int, (Int, Int))] = MapPartitionsRDD[22] at join at <console>:32
scala> println(pair.collect().mkString(","))
(3,(4,9)),(3,(6,9))
pairRDD.rightOuterJoin(other):对两个RDD进行连接操作,确保第一个RDD的键存在(右外连接)
scala> val pair = pairs.rightOuterJoin(other)
pair: org.apache.spark.rdd.RDD[(Int, (Option[Int], Int))] = MapPartitionsRDD[25] at rightOuterJoin at <console>:32
scala> println(pair.collect().mkString(","))
(3,(Some(4),9)),(3,(Some(6),9))
pairRDD.leftOuterJoin(other) :对两个RDD进行连接操作,确保第二个RDD的键必须存在(左外连接)
scala> val pair = pairs.leftOuterJoin(other)
pair: org.apache.spark.rdd.RDD[(Int, (Int, Option[Int]))] = MapPartitionsRDD[28] at leftOuterJoin at <console>:32
scala> println(pair.collect().mkString(","))
(1,(2,None)),(3,(4,Some(9))),(3,(6,Some(9)))
pairRDD.cogroup :将两个RDD中拥有相同键的数据分组到一起
scala> val pair = pairs.cogroup(other)
pair: org.apache.spark.rdd.RDD[(Int, (Iterable[Int], Iterable[Int]))] = MapPartitionsRDD[30] at cogroup at <console>:32
scala> println(pair.collect().mkString(","))
(1,(CompactBuffer(2),CompactBuffer())),(3,(CompactBuffer(4, 6),CompactBuffer(9)))
4.聚合操作
当数据以键值对形势组织的时候,聚合具有相同键的元素进行一些统计是很常见的操作。reduceByKey()和reduce相当类似,他们接受一个函数,并使用函数对值进行合并,reduceByKey()会为数据集中的每个键进行并行归约操作,每个归约器操作会将键相同的值合并起来。foldByKey()则与fold()相当类似,他们都使用一个与RDD和并函数中的数据结构相同的零值作为初始值。
在Scala中使用reduceByKey()和马匹Values() 和mapValues()计算每个键对应的平均值
rdd.mapValues(x=>(x,1)).reduceByKey((x,y)=>(x._1+y._1,x._2+y._2))
combineByKey():是最为常用的基于键进行整合的函数。可以返回与输入数据的类型不同的返回类型。由于combineByKey()会遍历分区中的所有元素。因此每个元素的键要么还没有遇到要么就和之前的某个元素的键相同。
如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值。需要注意的是,这样过程会在每个分区中第一次出现各个键时发生,而不是在整个RDD中第一次出现键时发生。如果这一处理键之前遇到过,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值合并,由于每个分区都是独立处理的,需要用户提供的mergeCombiners()方法将各个分区的结果合并
val result = input.combineByKey(
(v) => (v,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) //计算整个RDD
}.map{case(key,value)=>(key,value._1/value._2.toFloat)}
)
result.collectAsMap().map(ptintln())
5.并行调优
每个RDD都有固定数量的分区,分区数决定了RDD在进行调优时的并行度,我们可以指定并行度,不指定Spark会根据集群的大小推断一个有意义的默认值,
scala> val data = Seq(("a",3),("b",4),("a",1))
data: Seq[(String, Int)] = List((a,3), (b,4), (a,1))
scala> sc.parallelize(data).reduceByKey((x,y)=>x+y) //默认并行度
res22: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[46] at reduceByKey at <console>:32
scala> sc.parallelize(data).reduceByKey((x,y)=>x+y,10) //指定并行度10
res23: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[48] at reduceByKey at <console>:32