1、转化操作列表
针对两个pair RDD 的转化操作(rdd = {(1,2),(3,4),(3,6)} 为例)
函数名 | 目的 | 示例 | 结果 |
---|---|---|---|
reduceByKey(func) | 合并具有相同键的值 | rdd.reduceByKey((x,y)=> x+ y) | {(1,2),(3,10)} |
groupByKey() | 对具有相同键的值进行分组 | rdd.groupByKey() | {(1,[2]),(3,[4,6])} |
combineByKey( createCombiner, mergeValue, mergeCombiners, partitioner) | 使用不同的返回类型合并具有相同键的值 | ||
mapValues(func) | 对pair RDD中的每个值应用一个函数而不改变键 | rdd.mapValues(x=> x+1) | {(1,3),(3,5),(3,7)} |
flatMapValues(func) | 对pair RDD 中的每个值应用一个返回迭代器的函数,然后对返回的每个元素都生成一个对应原键的键值对记录。通常用于符号化 | rdd.flatMapValues(x=>(x to 5)) | { (1,2),(1,3),(1,4),(1,5),(3,4),(3,5),} |
keys() | 返回一个仅包含键的RDD | rdd.keys() | {1,3,3} |
values() | 返回一个仅包含值的RDD | rdd.values() | {2,4,6} |
sortByKey | 返回一个根据键排序的RDD | rdd.sortByKey() | {(1,2),(3,4),(3,6),} |
针对两个pair RDD 的转化操作(rdd = {(1,2),(3,4),(3,6)} other = {(3,9)})
函数名 | 目的 | 示例 | 结果 |
---|---|---|---|
subtractByKey | 删除RDD中键与other RDD 中的键相同的元素 | rdd.subtractByKey(other) | {(1,2)} |
join | 对两个RDD进行内连接 | rdd.join(other) | {(3,(4,9)),(3,(6,9))} |
rightOuterJoin | 对两个RDD进行连接操作,确保第一个RDD的键必须存在(右外连接) | rdd.rightOuterJoin(other) | {(3,(Some(4),9)),(3,(Some(6),9))} |
leftOuterJoin | 对两个RDD进行连接操作,确保第二个RDD的键必须存在(左外连接) | rdd.leftOuterJoin(other) | {(1,(2,None)), (3,(4,Some(9))), (3,(6,Some(9)))} |
cogroup | 将两个RDD中拥有相同键的数据分组到一起 | rdd.cogroup(other) | {(1,([2],[])), (3,([4,6],[9]))} |
2、聚合操作
当数据集以键值对形式组织的时候,聚合具有相同键的元素进行一些统计是很常见的操作。之前讲解过基础RDD上的fold()、combine()、reduce()等行动操作,pair RDD上则有相应的针对键的转化操作。Spark 有一组类似的操作,可以组合具有相同键的值。这些操作返回RDD,因此它们是转化操作而不是行动操作。
reduceByKey() 与 reduce() 相当类似;它们都接收一个函数,并使用该函数对值进行合并。 reduceByKey() 会为数据集中的每个键进行并行的归约操作,每个归约操作会将键相同的值合并起来。因为数据集中可能有大量的键,所以 reduceByKey() 没有被实现为,向用户程序返回一个值的行动操作。实际上,它会返回一个由各键和对应键归约出来的结果值组成的新的 RDD。
foldByKey() 则与 fold() 相当类似;它们都使用一个RDD 和合并函数中的 数据类型相同的 零值作为初始值。与 fold() 一样,foldByKey() 操作所使用的合并函数对零值与另一 个元素进行合并,结果仍为该元素。
(1)求均值 方法一
scala> val inputRDD = sc.makeRDD(List( \
("panda",0),("pink",3),("pirate",3),("panda",1),("pink",4)))
inputRDD: org.apache.spark.rdd.RDD[(String, Int)]
= ParallelCollectionRDD[6] at makeRDD at <console>:24
scala> val resultRDD = inputRDD.mapValues(x => (x, 1)).reduceByKey( \
(x, y) => (x._1 + y._1, x._2 + y._2)).map{ \
case (key, value) => (key, value._1 / value._2.toFloat) }
resultRDD: org.apache.spark.rdd.RDD[(String, Float)]
= MapPartitionsRDD[9] at map at <console>:26
scala> resultRDD.collect
res3: Array[(String, Float)] = Array((panda,0.5), (pink,3.5), (pirate,3.0))
combineByKey() 是最为常用的基于键进行聚合的函数。大多数基于键聚合的函数都是用它实现的。和 aggregate() 一样,combineByKey() 可以让用户返回与输入数据的类型不同的 返回值
要理解 combineByKey(),要先理解它在处理数据时是如何处理每个元素的。由于 combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就 和之前的某个元素的键相同
如果这是一个新的元素,combineByKey() 会使用一个叫作 createCombiner() 的函数来创建 那个键对应的累加器的初始值。需要注意的是,这一过程会在每个分区中第一次出现各个 键时发生,而不是在整个 RDD 中第一次出现一个键时发生
如果这是一个在处理当前分区之前已经遇到的键,它会使用 mergeValue() 方法将该键的累 加器对应的当前值与这个新的值进行合并
由于每个分区都是独立处理的,因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器,就需要使用用户提供的 mergeCombiners() 方法将各 个分区的结果进行合并。
(2)求均值 方法二
scala> val inputRDD = sc.makeRDD(List( \
("panda",0),("pink",3),("pirate",3),("panda",1),("pink",4)))
inputRDD: org.apache.spark.rdd.RDD[(String, Int)]
= ParallelCollectionRDD[6] at makeRDD at <console>:24
scala> val resultRDD = inputRDD.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)
).map{ case (key, value) => (key, value._1 / value._2.toFloat) }
resultRDD: org.apache.spark.rdd.RDD[(String, Float)]
= MapPartitionsRDD[11] at map at <console>:26
scala> resultRDD.collect
res5: Array[(String, Float)] = Array((panda,0.5), (pink,3.5), (pirate,3.0))
scala> resultRDD.collectAsMap.map(println _)
(pirate,3.0)
(pink,3.5)
(panda,0.5)
res6: Iterable[Unit] = ArrayBuffer((), (), ())
3、数据分组
如果数据已经以预期的方式提取了键,groupByKey() 就会使用 RDD 中的键来对数据进行 分组。对于一个由类型 K 的键和类型 V 的值组成的 RDD,所得到的结果 RDD 类型会是 [K, Iterable[V]]。
groupBy() 可以用于未成对的数据上,也可以根据除键相同以外的条件进行分组。它可以 接收一个函数,对源 RDD 中的每个元素使用该函数,将返回结果作为键再进行分组。
多个RDD分组,可以使用cogroup函数,cogroup() 的函数对多个共享同 一个键的 RDD 进行分组。对两个键的类型均为 K 而值的类型分别为 V 和 W 的 RDD 进行 cogroup() 时,得到的结果 RDD 类型为 [(K, (Iterable[V], Iterable[W]))]。如果其中的 一个 RDD 对于另一个 RDD 中存在的某个键没有对应的记录,那么对应的迭代器则为空。 cogroup() 提供了为多个 RDD 进行数据分组的方法。
4、连接
连接主要用于多个Pair RDD的操作,连接方式多种多样:右外连接、左外连接、交叉连接以及内连接。
普通的 join 操作符表示内连接,生成的pair RDD会包括来自两个输入RDD的每一组相对应的记录。
leftOuterJoin()产生的pair RDD中,来源于RDD的每一个键都有对应的记录。每个键相应的值是由一个源 RDD 中的值与一个包含第二个 RDD 的值的 Option(在 Java 中为 Optional)对象组成的二元组。
rightOuterJoin() 几乎与 leftOuterJoin() 完全一样,只不过预期结果中的键必须出现在 第二个 RDD 中,而二元组中的可缺失的部分则来自于源 RDD 而非第二个 RDD。
scala> val storeA = sc.makeRDD(List(("AA",12),("AA",13),("cc",14),("dd",15)))
storeA: org.apache.spark.rdd.RDD[(String, Int)]
= ParallelCollectionRDD[12] at makeRDD at <console>:24
scala> val storeB = sc.makeRDD(List(("cc",17),("AA",16)))
storeB: org.apache.spark.rdd.RDD[(String, Int)]
= ParallelCollectionRDD[13] at makeRDD at <console>:24
scala> storeA.join(storeB).collect
res7: Array[(String, (Int, Int))] = Array((AA,(12,16)), (AA,(13,16)), (cc,(14,17)))
scala> storeA.leftOuterJoin(storeB).collect
res9: Array[(String, (Int, Option[Int]))]
= Array((dd,(15,None)), (AA,(12,Some(16))), (AA,(13,Some(16))), (cc,(14,Some(17))))
scala> storeA.rightOuterJoin(storeB).collect
res10: Array[(String, (Option[Int], Int))]
= Array((AA,(Some(12),16)), (AA,(Some(13),16)), (cc,(Some(14),17)))
5、数据排序
sortByKey() 函数接收一个叫作 ascending 的参数,表示我们是否想要让结果按升序排序(默认值为 true)。