Spark Pair RDD键值对操作
1 PairRDD简介
2 创建Pair RDD
3 Pair RDD的转化操作
3.1 聚合操作
1 PairRDD简介
Spark 为包含键值对类型的RDD 提供了一些专有的操作。这些RDD 被称为pair RDD1。PairRDD 是很多程序的构成要素,因为它们提供了并行操作各个键或跨节点重新进行数据分组的操作接口。例如,pair RDD 提供reduceByKey() 方法,可以分别归约每个键对应的数据,还有join() 方法,可以把两个RDD 中键相同的元素组合到一起,合并为一个RDD。我们通常从一个RDD 中提取某些字段(例如代表事件时间、用户ID 或者其他标识符的字段),并使用这些字段作为pair RDD 操作中的键。
2 创建Pair RDD
在Spark 中有很多种创建pair RDD 的方式,很多存储键值对的数据格式会在读取时直接返回由其键值对数据组成的pair RDD。此外,当需要把一个普通的RDD 转为pair RDD 时,可以调用map() 函数来实现,传递的函数需要返回键值对。后面会展示如何将由文本行组成的RDD 转换为以每行的第一个单词为键的pair RDD。
在Scala 中,为了让提取键之后的数据能够在函数中使用,需要返回二元组。隐式转换可以让二元组RDD 支持附加的键值对函数。
scala> var lines = sc.textFile("passwd")
lines: org.apache.spark.rdd.RDD[String] = passwd MapPartitionsRDD[1] at textFile at <console>:27
scala> var pair = lines.map(x => (x.split(":")(0),x))
pair: org.apache.spark.rdd.RDD[(String, String)] = MapPartitionsRDD[2] at map at <console>:29
scala> pair.take(3).foreach(println)
(root,root:x:0:0:root:/root:/bin/bash)
(bin,bin:x:1:1:bin:/bin:/sbin/nologin)
(daemon,daemon:x:2:2:daemon:/sbin:/sbin/nologin)
当用Scala 从一个内存中的数据集创建pair RDD 时,只需要对这个由二元组组成的集合调用SparkContext.parallelize() 方法。
3 Pair RDD的转化操作
Pair RDD 可以使用所有标准RDD 上的可用的转化操作。由于pair RDD 中包含二元组,所以需要传递的函数应当操作二元组而不是独立的元素。
Pair RDD的转化操作(以键值对集合{(1, 2), (3, 4), (3, 6)}为例)
对两个pair RDD的转化操作(rdd = {(1, 2), (3, 4), (3, 6)}other = {(3, 9)})
scala> var lines = sc.textFile("passwd")
lines: org.apache.spark.rdd.RDD[String] = passwd MapPartitionsRDD[6] at textFile at <console>:27
scala> var pair = lines.map(x => (x.split(":")(0),x.split(":")(6)))
pair: org.apache.spark.rdd.RDD[(String, String)] = MapPartitionsRDD[7] at map at <console>:29
scala> pair.take(3).foreach(println)
(root,/bin/bash)
(bin,/sbin/nologin)
(daemon,/sbin/nologin)
scala> var bash = pair.filter(u => u._2.contains("bash"))
bash: org.apache.spark.rdd.RDD[(String, String)] = MapPartitionsRDD[8] at filter at <console>:31
scala> bash.take(3).foreach(println)
(root,/bin/bash)
(hdfs,/bin/bash)
(yarn,/bin/bash)
3.1 聚合操作
当数据集以键值对形式组织的时候,聚合具有相同键的元素进行一些统计是很常见的操作。之前讲解过基础RDD 上的fold()、combine()、reduce() 等行动操作,pair RDD 上则有相应的针对键的转化操作。Spark 有一组类似的操作,可以组合具有相同键的值。这些操作返回RDD,因此它们是转化操作而不是行动操作。
reduceByKey() 与reduce() 相当类似;它们都接收一个函数,并使用该函数对值进行合并。reduceByKey() 会为数据集中的每个键进行并行的归约操作,每个归约操作会将键相同的值合并起来。因为数据集中可能有大量的键,所以reduceByKey() 没有被实现为向用户程序返回一个值的行动操作。实际上,它会返回一个由各键和对应键归约出来的结果值组成的新的RDD。
foldByKey() 则与fold() 相当类似;它们都使用一个与RDD 和合并函数中的数据类型相同的零值作为初始值。与fold() 一样,foldByKey() 操作所使用的合并函数对零值与另一个元素进行合并,结果仍为该元素。
可以使用reduceByKey() 和mapValues() 来计算每个键的对应值的均值
scala> val rdd = sc.parallelize(List(("panda",0),("pink",3),("pirate",3),("panda",1),("pink",4)))
rdd: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[9] at parallelize at <console>:27
scala> val kv = rdd.mapValues(x => (x, 1)).reduceByKey((x, y) => (x._1 + y._1, x._2 + y._2)).map(v => (v._1,v._2._1/v._2._2.toDouble))
kv: org.apache.spark.rdd.RDD[(String, Double)] = MapPartitionsRDD[17] at map at <console>:29
scala> kv.collect().foreach(println)
(panda,0.5)
(pink,3.5)
(pirate,3.0)
熟悉MapReduce 中的合并器(combiner)概念的读者可能已经注意到,调用reduceByKey() 和foldByKey() 会在为每个键计算全局的总结果之前先自动在每台机器上进行本地合并。用户不需要指定合并器。更泛化的combineByKey() 接口可以让你自定义合并的行为。
PairRDD 的行动操作(以键值对集合{(1,2),(3,4),(3,6)})