前言
你们好我是啊晨
现在继续更新spark core spark核心。
废话不多说,内容很多选择阅读就好,很详细。
请:
三、键值对RDD
键值对 RDD 是 Spark 中许多操作所需要的常见数据类型。 本章做特别讲解。除了在基础RDD类中定义的操作之外,Spark 为包含键值对类型的 RDD 提供了一些专有的操作 在PairRDDFunctions专门进行了定义。这些 RDD 被称为 pair RDD。
有很多种方式创建pair RDD,在输入输出章节会讲解。一般如果从一个普通的RDD转 为pair RDD时,可以调用map()函数来实现,传递的函数需要返回键值对。
val pairs = lines.map(x => (x.split(" ")(0), x))
1、键值对RDD的转化操作
(1)转化操作列表
(2)聚合操作
当数据集以键值对形式组织的时候,聚合具有相同键的元素进行一些统计是很常见的操 作。之前讲解过基础RDD上的fold()、combine()、reduce()等行动操作,pair RDD上则 有相应的针对键的转化操作。Spark 有一组类似的操作,可以组合具有相同键的值。这些 操作返回 RDD,因此它们是转化操作而不是行动操作。
reduceByKey() 与 reduce() 相当类似;它们都接收一个函数,并使用该函数对值进行合并。 reduceByKey() 会为数据集中的每个键进行并行的归约操作,每个归约操作会将键相同的值合 并起来。因为数据集中可能有大量的键,所以 reduceByKey() 没有被实现为向用户程序返回一 个值的行动操作。实际上,它会返回一个由各键和对应键归约出来的结果值组成的新的 RDD。
foldByKey() 则与 fold() 相当类似;它们都使用一个与 RDD 和合并函数中的数据类型相 同的零值作为初始值。与 fold() 一样,foldByKey() 操作所使用的合并函数对零值与另一 个元素进行合并,结果仍为该元素。
关于数据初始值
初始化是对每个数据进行操作,这其实是错误的。应该是针对每个partition中,每个key下的都有一个初始值。这句话怎么理解呢?看代码:
三个分区
val rdd07 = sc.makeRDD(Array(("a",1),("a",5),("b",2),("c",3),("a",3),("b",5),("c",1),("c",3),("a",5),("a",5)),3)
val rdd25 = rdd07.foldByKey(1)(_+_)
rdd07.mapPartitionsWithIndex((index,items)=>Iterator(index+":"+items.mkString("、"))).collect().foreach(println(_))
println(rdd07.collect().toBuffer)
println(rdd25.collect().toBuffer)
两个分区
val rdd07 = sc.makeRDD(Array(("a",1),("a",5),("b",2),("c",3),("a",3),("b",5),("c",1),("c",3),("a",5),("a",5)),2)
val rdd25 = rdd07.foldByKey(1)(_+_)
rdd07.mapPartitionsWithIndex((index,items)=>Iterator(index+":"+items.mkString("、"))).collect().foreach(println(_))
println(rdd07.collect().toBuffer)
println(rdd25.collect().toBuffer)
求均值操作:版本一
input.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) }
combineByKey() 是最为常用的基于键进行聚合的函数。大多数基于键聚合的函数都是用它 实现的。和 aggregate() 一样,combineByKey() 可以让用户返回与输入数据的类型不同的 返回值。
要理解 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)
).map{
case (key, value) => (key, value._1 / value._2.toFloat) }
result.collectAsMap().map(println(_))
combineByKey是针对不同partition进行操作的。它的第一个参数用于数据初始化(后面着重讲),第二个是针对一个partition的combine操作函数,第三个是在所有partition都combine完毕后,针对所有临时结果进行combine操作的函数。
关于数据初始化
之前有人说,初始化是对每个数据进行操作,这其实是错误的。应该是针对每个partition中,每个key下的第一个数据进行操作。这句话怎么理解呢?看代码:
val rdd1 = sc.parallelize(List(1,2,2,3,3,3,3,4,4,4,4,4), 2)
val rdd2 = rdd1.map((_, 1))
val rdd3 = rdd2.combineByKey(-_, (x:Int, y:Int) => x + y, (x:Int, y:Int) => x + y)
val rdd4 = rdd2.combineByKey(+_, (x:Int, y:Int) => x + y, (x:Int, y:Int