RDD,即弹性分布式数据集,全称为Resilient Distributed Dataset,是一个容错的,并行的数据结构,可以让用户显式地 将数据存储到磁盘和内存中,并能控制数据的分区。同时,RDD还提供了一组非常丰富的操作来操作这些数据,如:map,flatMap,filter等转换操作,以及SaveAsTextFile,conutByKey等行动操作。
本博客中将详细讲解RDD的转换操作的各种运算方法。
map是对RDD中的每一个元素都执行一个指定的函数来产生一个新的RDD,RDD之间的元素是一对一的关系。
val rdd1: RDD[Int] = sc.parallelize(1 to 9, 3)
val rdd2: RDD[Int] = rdd1.map(_ * 2)
printResult("map", rdd2)
结果:map >> List(2, 4, 6, 8, 10, 12, 14, 16, 18)
flatMap类似于map,但是每一个输入元素,会被映射为0到多个出输出元素(即func函数的返回值是一个Seq,而不是单一元素)的新的RDD,RDD之间的元素是一对多关系。
val rdd3: RDD[Int] = rdd2.filter(x => x > 10).flatMap(x => x to 21)
printResult("flatMap", rdd3)
结果:flatMap >> List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 14, 15, 16, 17, 18, 19, 20, 21, 16, 17, 18, 19, 20, 21, 18, 19, 20, 21)
filter是对RDD元素进行过滤,返回一个新的数据集,有经过func函数后返回值为true的元素组成。
val rdd4 = rdd2.filter(x => x > 11)
printResult("filter", rdd4)
结果:filter >> List(12, 14, 16, 18)
mapPartitions是map的一个变种。map的输入函数应用于RDD中的每一个元素,而mapPartitions的输入函数应用于每一个分区的数据,也就是把每一个分区中的内容作为整体来处理。
它的函数定义为:
def mapPartitions[U: ClassTag](f: Iterator[T] => Iterator[U],preservesPartitioning: Boolean = false): RDD[U]
val rdd5: RDD[(Int, Int)] = rdd1.repartition(2).mapPartitions((iter: Iterator[Int]) => {
val lst: ListBuffer[(Int, Int)] = new ListBuffer[(Int, Int)]()
var prev: Int = 0
var current: Int = 0
while (iter.hasNext) {
current = iter.next
lst += ((prev, current))
prev = current
}
lst.iterator
})
printResult("mapPartitions", rdd5)
结果:mapPartitions >> List((0,1), (1,3), (3,5), (5,7), (7,9), (0,2), (2,4), (4,6), (6,8))
mapPartitionsWithIndex于mapPartitions的功能类似,只是多传入split index而已,所有func函数必须是(Int,Iterator
val rdd6 = rdd1.repartition(2).mapPartitionsWithIndex((idx, iter) => {
val lst = new ListBuffer[String]()
var sum = 0
while (iter.hasNext) {
sum += iter.next
}
lst += (idx + ":" + sum)
lst.iterator
})
printResult("mapPartitionsWithIndex", rdd6)
结果:mapPartitionsWithIndex >> List(0:25, 1:20)
sample(withReplacemet,fraction,seed)是根据给定的随机种子seed,随机抽样出数量为fraction的数据。其中,withReplacement:是否放回抽样;fraction:比抽样比例,0.1表示抽样10%,seed:随机种子,相同的seed得到的随机序列是一样的。所以,如果没有设置seed,同一段代码执行两遍得到的随机序列是一样的。
val sampleRDD = sc.parallelize(0 to 1000, 2)
val rdd7 = sampleRDD.sample(false, 0.02, 2)
printResult("sample", rdd7)
结果:sample >> List(10, 42, 110, 121, 145, 158, 166, 234, 237, 253, 266, 343, 354, 393, 404, 457, 460, 662, 728, 738, 806, 808, 868, 887, 889, 934, 952)
union(otherDataset)是数据的合并,返回一个新的数据集,由原数据集和otherDataset合并而成的一个数据集RDD。
val rdd8 = rdd1.union(rdd2)
printResult("union", rdd8)
结果:union >> List(1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 4, 6, 8, 10, 12, 14, 16, 18)
intersection(otherDataset)是数据交集,返回一个新的数据集,它是两个数据集的交集数据。
val rdd9 = rdd8.intersection(rdd1)
printResult("intersection", rdd9)
结果:intersection >> List(4, 8, 1, 9, 5, 6, 2, 3, 7)
distince(numPartitions)是对数据集进行去重,返回一个新的数据集,它是对两个数据集去掉重复数据后得到的一个数据集。其中,numPartitions参数是设置任务并行数量。
val rdd10 = rdd8.union(rdd9).distinct(2)
printResult("distinct", rdd10)
结果:distinct >> List(4, 16, 14, 6, 8, 12, 18, 10, 2, 1, 3, 7, 9, 5)
groupByKey(partitioner)是对数据进行分组操作,在一个由(K,V)对组成的数据集上调用,返回一个(K,Seq[V])对的数据集。注意,默认情况下,使用8个并行任务进行分组,可以传入partitioner参数设置并行任务的分区数。
val rddMap: RDD[(Int, Int)] = rdd1.map(item => (item % 3, item))
val rdd11 = rddMap.groupByKey();
printResult("groupByKey", rdd11)
结果:groupByKey >> List((0,CompactBuffer(3, 6, 9)), (2,CompactBuffer(2, 5, 8)), (1,CompactBuffer(1, 4, 7)))
reduceByKey(func, numPartitions)是对数据进行分组聚合操作,在一个(K,V)对的数据集上使用,返回一个(K,V)对的数据集。Key是相同的值,都被使用指定的reduce函数聚合到一起。和groupByKey类似,可以通过参数numPartitions设置并行任务的分区数。
val rdd12 = rddMap.reduceByKey((x, y) => x + y)
printResult("reduceByKey", rdd12)
结果:reduceByKey >> List((0,18), (2,15), (1,12))
aggregateByKey(zeroValue, numPartitions)(seqOp: (U, V) => U,combOp: (U, U) => U)和reduceByKey的不同在于:reduceByKey输入/输出都是(K,V),而aggregateByKey输出是(K,U),可以不同于输入(K,U)。
aggregateByKey的三个参数如下:
zeroValue:U,初始值,比如初始值为0或空列表。
numPartitions:指定并行任务的分区数。
seqOp:(U,V)=>U,seq操作符,描述如何将T合并入U,比如如何将item合并到列表中。
combOp: (U, U) => U,comb操作符,描述如何合并两个U,比如合并两个列表。
所以,可以将aggregateByKey函数抽象成更高级的,更灵活的reduce和group的组合。
val rdd13 = rddMap.aggregateByKey(0)(
seqOp = (x, y) => {
math.max(x, y)
},
combOp = (x, y) => {
x + y
})
printResult("aggregateByKey", rdd13)
结果:aggregateByKey >> List((0,12), (2,10), (1,11))
combineByKey(createCombiner: V => C,mergeValue: (C, V) => C,mergeCombiners: (C, C) => C,numPartitions: Int)是对RDD中的数据按照Key进行聚合操作。聚合操作的逻辑是通过自定义函数提供给combineByKey。 把(K,V)类型的RDD转换为(K,C)类型的RDD,C和Vk可以不一样。
combineyey的三个参数如下:
createCombiner:在遍历(K,V)是,如果combineByKey的第一次遇到值为K的key(类型K),那么将对这个(K,V)调用createCombiner函数,将V转换为c(类型C,聚合对象的类型)。
mergeValue:在遍历(k,v)是,如果comineByKey不是第一次遇到值为k的key(类型为K),那么将对这个(k,v)调用mergeValue函数,它的作用是将v累加到聚合对象(类型C)中,mergeValue的类型是(C,V)=>C,参数中的C遍历到此处的聚合对象,然后对v进行聚合得到新的聚合对象值。
mergeCombiners:combineByKey实在分布式环境中执行的,RDD的每个分区单独进行combineBykey操作,最后需要对各个分区的据俄国进行最后的聚合。它的函数类型是(C,C)=>C,每个参数是分区聚合得到的聚合对象。
val rdd14 = rdd1.map(item => (item % 4, item))
.mapValues(v => v.toDouble)
.combineByKey((v: Double) => (v, 1),
(c: (Double, Int), v: Double) => (c._1 + v, c._2 + 1),
(c1: (Double, Int), c2: (Double, Int)) => (c1._1 + c2._1, c1._2 + c2._2))
printResult("combineByKey", rdd14)
结果:combineByKey >> List((0,(12.0,2)), (2,(8.0,2)), (1,(15.0,3)), (3,(10.0,2)))
sortByKey(ascending, numPartitions)是对RDD中的数据集进行排序操作,对(K,V)类型的数据按照K进行排序,其中K需要实现Ordered方法。
第一个参数是ascending,该参数决定排序后RDD中的元素是升序还是降序,默认是true,按升序排序。
第二个参数是numPartitions,即排序分区的并行任务个数。
val rdd15 = rddMap.sortByKey(false)
printResult("sortByKey", rdd15)
结果:sortByKey >> List((2,2), (2,5), (2,8), (1,1), (1,4), (1,7), (0,3), (0,6), (0,9))
join(other, partitioner)是连接操作,将数据的数据集(K,V)和另外一个数据集(K,W)进行join,得到(K,(V,W))。该操作是对于相同K的V和W集合进行笛卡尔积操作,也即V和W的所有组合。连接操作除了join外,还有左连接,右连接,全连接操作函数:leftOuterJoin,rightOuterJoin和fullOuterJoin,它们的用法基本上是一样的,
val rddMap1 = rdd1.map(item => (item % 4, item))
val rdd16 = rddMap.join(rddMap1)
printResult("join", rdd16)
结果:join >> List((0,(3,4)), (0,(3,8)), (0,(6,4)), (0,(6,8)), (0,(9,4)), (0,(9,8)), (2,(2,2)), (2,(2,6)), (2,(5,2)), (2,(5,6)), (2,(8,2)), (2,(8,6)), (1,(1,1)), (1,(1,5)), (1,(1,9)), (1,(4,1)), (1,(4,5)), (1,(4,9)), (1,(7,1)), (1,(7,5)), (1,(7,9)))
cogroup(other: RDD[(K, W)], partitioner: Partitioner)是将RDD中的输入数据集(K,V)和另外一个数据集(K,W)进行cogroup,得到一个格式为(K,Seq[V],Seq[W])的数据集
val rdd17 = rddMap.cogroup(rddMap1)
printResult("cogroup", rdd17)
结果:cogroup >> List((0,(CompactBuffer(3, 6, 9),CompactBuffer(4, 8))), (2,(CompactBuffer(2, 5, 8),CompactBuffer(2, 6))), (1,(CompactBuffer(1, 4, 7),CompactBuffer(1, 5, 9))), (3,(CompactBuffer(),CompactBuffer(3, 7))))
cartesian(other: RDD[U])是对两个数据集T和U进行笛卡尔积操作,得到(T,U)格式的数据集。
val rdd18 = rddMap.cartesian(rddMap1)
printResult("cartesian", rdd18)
结果:cartesian >> List(((1,1),(1,1)), ((1,1),(2,2)), ((1,1),(3,3)), ((1,1),(0,4)), ((2,2),
(1,1)), ((2,2),(2,2)), ((2,2),(3,3)), ((2,2),(0,4)), ((0,3),(1,1)), ((0,3),(2,2)), ((0,3),
(3,3)), ((0,3),(0,4)), ((1,4),(1,1)), ((1,4),(2,2)), ((1,4),(3,3)), ((1,4),(0,4)), ((1,1),
(1,5)), ((1,1),(2,6)), ((1,1),(3,7)), ((1,1),(0,8)), ((1,1),(1,9)), ((2,2),(1,5)), ((2,2),
(2,6)), ((2,2),(3,7)), ((2,2),(0,8)), ((2,2),(1,9)), ((0,3),(1,5)), ((0,3),(2,6)), ((0,3),
(3,7)), ((0,3),(0,8)), ((0,3),(1,9)), ((1,4),(1,5)), ((1,4),(2,6)), ((1,4),(3,7)), ((1,4),
(0,8)), ((1,4),(1,9)), ((2,5),(1,1)), ((2,5),(2,2)), ((2,5),(3,3)), ((2,5),(0,4)), ((0,6),
(1,1)), ((0,6),(2,2)), ((0,6),(3,3)), ((0,6),(0,4)), ((1,7),(1,1)), ((1,7),(2,2)), ((1,7),
(3,3)), ((1,7),(0,4)), ((2,8),(1,1)), ((2,8),(2,2)), ((2,8),(3,3)), ((2,8),(0,4)), ((0,9),
(1,1)), ((0,9),(2,2)), ((0,9),(3,3)), ((0,9),(0,4)), ((2,5),(1,5)), ((2,5),(2,6)), ((2,5),
(3,7)), ((2,5),(0,8)), ((2,5),(1,9)), ((0,6),(1,5)), ((0,6),(2,6)), ((0,6),(3,7)), ((0,6),
(0,8)), ((0,6),(1,9)), ((1,7),(1,5)), ((1,7),(2,6)), ((1,7),(3,7)), ((1,7),(0,8)), ((1,7),
(1,9)), ((2,8),(1,5)), ((2,8),(2,6)), ((2,8),(3,7)), ((2,8),(0,8)), ((2,8),(1,9)), ((0,9),
(1,5)), ((0,9),(2,6)), ((0,9),(3,7)), ((0,9),(0,8)), ((0,9),(1,9)))
pipe(command: Seq[String],env: Map[String, String])是以shell命令处理RDD数据。
val rdd19: RDD[String] = rdd1.pipe("head -n 1")
printResult("pipe", rdd19)
结果:pipe >> List(1,4,7)
randomSplit(weights: Array[Double],seed: Long = Utils.random.nextLong)是对RDD按照权重进行数据分割,第一个参数为分割权重数组,第二参数为随机种子,该方法通常用于样本的划分。
val rdd20: Array[RDD[Int]] = rdd1.randomSplit(Array(0.3, 0.7), 1)
rdd20.foreach(rdd => {
printResult("randomSplit", rdd)
})
结果:randomSplit >> List(1, 3, 4, 5, 6, 7, 9), List(2, 8)
subtract(other: RDD[T], numPartitions: Int)是RDD对other数据集进行减法操作,将输入的rdd中的元素减去other中的元素,得到他们差值的一个新的RDD。
val rdd21 = rdd1.subtract(sc.parallelize(1 to 5))
printResult("subtract", rdd21)
结果:subtract >> List(6, 9, 7, 8)
zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]是对两个RDD进行拉链操作,得到一个新的RDD[T,U]。拉链操作还包括:zipWitIndex和zipPartitinos。
val rdd22 = rdd1.repartition(3).zip(sc.parallelize(Array("A", "B", "C", "D", "E", "F", "G", "H", "I"), 3))
printResult("zip", rdd22)
结果;zip >> List((3,A), (6,B), (8,C), (1,D), (4,E), (9,F), (2,G), (5,H), (7,I))
coalesce(numPartitions: Int)是对RDD进行重分区,默认不进行shuffle,且该RDD的分区个数等于numPartitions个数。
val rdd23 = rdd1.coalesce(2)
printResult("coalesce", rdd23)
结果:coalesce >> List(1, 3, 4, 6, 7, 9, 2, 5, 8)
repartition(numPartitions: Int)是将RDD进行重新分区,分区过程中会进行shuffle,调整分区数量为numPartitions。
val rdd24 = rdd1.repartition(2)
printResult("repartition", rdd24)
结果:repartition >> List(1, 3, 4, 6, 7, 9, 2, 5, 8)
treeAggregate[U](zeroValue: U)(seqOp: (U, T) => U,combOp: (U, U) => U,depth: Int = 2)是采用多层树模式进行RDD的聚合操作,类似于aggregateByKey。
treeAggregate的是三个参数说明如下:
zeroValue:U,初始值,可以是0或空列表等。
seqOp:(U,T)=>U,seq操作符,描述如何将T合并入U,比如将item合并到列表中。
combOp: (U, U),comb操作符,描述如何合并两个U,比如将两个列表进行合并。
val rdd25: (Int, Int) = rddMap.treeAggregate(0, 0)(
seqOp = (u, t) => {
(u._1 + t._1, u._2 + t._2)
},
combOp = (u1, u2) => {
(u1._1 + u2._1, u1._2 + u2._2)
})
println("treeAggregate>> " + rdd25)
结果:treeAggregate>> (9,45)