11种 Spark RDD Word Count 方法
总结
一、5种 k-v聚合(转换算子)
相同底层函数combineByKeyWithClassTag
:
reduceByKey
,groupByKey
,aggregateByKey
,foldByKey
,combineByKey
集合外元素概念*2
aggregateByKey
定制化程度最高,3参数为:零值(集合外元素),分区内数据合并方法,不同分区间聚合结果合并方法
foldByKey
是 aggregateByKey
的简化版/参数严格版 均与来源RDD类型相同,2参数为:零值(集合外元素),分区内==分区间
只·集合内元素*2
combineByKey
只在集合内部做聚合,没有集合外元素概念,对遇到的第一个Key的Value对象进行结构转换。3参数
reduceByKey
是 combineByKey
的简化版,1参数为 分区内方法==分区见方法
只·分组不聚合*1
groupByKey
只分组(做一半) 结果为 (String, Iterable[Int]),(另一半)聚合使用map(Iterable.size/sum)。无参数
二、3种 单值聚合(1转换+2行动算子)
groupBy
虽然叫单值转换算子,实质仍是groupByKey,但模拟K-V处理时groupBy结果为 (String, Iterable[(String, Int)]),groupByKey为(String, Iterable[Int])。1参数为分组字段
使用map,舍弃Iterable String,使用元组 String + 其对应的 Iterable[Int],Iterable.sum/size聚合
a,((a,1),(a,1),(a,1)) => a,(1,1,1) => a,3 =1+1+1/(1,1,1).size
aggregate
行动算子,3参数,集合外元素:可变Map,同分区内合并方法,不同分区间聚合结果合并方法
相较于aggregateByKey只需写v的合并规则,aggregate需要自己写完整k,v处理方法(aggregateByKey简陋版)
相较于groupBy少做一步map,因为其集合外元素作为新结构基础
fold
是aggregate的简化版/参数严格版 均与来源RDD类型相同,行动算子,2参数,集合外元素:可变Map,分区内==分区间
相较于aggregate,其必须先多做一步map,将RDD[String] -> RDD[mutable.Map(Stirng,Int)]
三、2种 行动算子
countByKey
函数实质:mapValues(_ => 1L).reduceByKey(+).collect().toMap
countByValue
函数实质:map(value => (value, null)).countByKey()
由函数实质,可推,表面countByValue比countByKey少写一步map(单值->KV),其实是底层帮写了。
二者总结:
二者最终结果均为 collection.Map,可推,只当结果Map很小时才可用此方法,因为整个Map都被加载到Driver内存
四、1种 自定义累加器
实质:每个Task都有一个可变Map对象,对每个Task数据进行遍历存入可变Map,然后再跨Task合并可变Map对象即可。
结果一览
(Hello,3)
(afternoon,1)
(morning,1)
(good,2)
(Word,1)
(Scala,1)
------ ReduceByKey ------
(Hello,3)
(afternoon,1)
(morning,1)
(good,2)
(Word,1)
(Scala,1)
------ GroupByKey ------
(Hello,3)
(afternoon,1)
(Word,1)
(morning,1)
(Scala,1)
(good,2)
------ aggregateByKey ------
(Hello,3)
(afternoon,1)
(morning,1)
(good,2)
(Word,1)
(Scala,1)
------ foldByKey ------
(Hello,3)
(afternoon,1)
(morning,1)
(good,2)
(Word,1)
(Scala,1)
------ combineByKey ------
(Hello,3)
(afternoon,1)
(morning,1)
(good,2)
(Word,1)
(Scala,1)
------ GroupBy ------
(Word,1)
(Hello,3)
(Scala,1)
(morning,1)
(good,2)
(afternoon,1)
------ Aggregate ------
(Word,1)
(Hello,3)
(morning,1)
(Scala,1)
(good,2)
(afternoon,1)
------ Fold ------
(good,2)
(Scala,1)
(Hello,3)
(afternoon,1)
(morning,1)
(Word,1)
------ CountByKey ------
(good,2)
(Scala,1)
(Hello,3)
(afternoon,1)
(morning,1)
(Word,1)
------ CountByValue ------
Map(Word -> 1, Hello -> 3, Scala -> 1, morning -> 1, afternoon -> 1, good -> 2)
------ MyAccumulator ------
完整源码
package com.inbreeze.bigdata.spark.core
import org.apache.spark.rdd.RDD
import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable
object WC_12_Summary {
def main(args: Array[String]): Unit = {
//TODO 0 准备
//0.1 原始数据
val strs: Array[String] = Array("Hello good morning", "Hello good afternoon", "Hello Scala Word")
//0.2 sc 对象
val scConf: SparkConf = new SparkConf().setAppName("WC_12_Summary").setMaster("local[*]")
val sc: SparkContext = new SparkContext(scConf)
sc.setLogLevel("ERROR")
//TODO 1 K-V聚合全家桶:reduceByKey -- 根据 Key1==Key2 shuffle 聚合 V
//笔记1:reduceByKey,在shuffle之前有combine(分区内预聚合)操作
//笔记2:reduceByKey,同一分区内预聚合规则 == 不同分区数据聚合规则 (shuffle,来自不同分区数据的聚合)
/*
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)]
combineByKeyWithClassTag[V]((v: V) => v, func, func, partitioner)
*/
sc.makeRDD(strs).flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).collect().foreach(println)
println("------ ReduceByKey ------")
//TODO 2 K-V聚合全家桶:groupByKey -- 根据 Key1==Key2 shuffle 分组 V(Iterable集合) 然后对集合求sum
//笔记1:groupByKey,直接进行shuffle,将在内存中缓存任意key的所有K-V对,如果一个Key有多个Value 可能会导致内存溢出
//笔记2:groupByKey,如果 group分组的目的是——对V聚合操作,如 sum/average 等,则应使用 aggregateByKey/reduceByKey
/*
def groupByKey(): RDD[(K, Iterable[V])]
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
combineByKeyWithClassTag[CompactBuffer[V]](createCombiner, mergeValue,
mergeCombiners, partitioner, mapSideCombine = false)
*/
val groupByKeyRDD: RDD[(String, Iterable[Int])] = sc.makeRDD(strs).flatMap(_.split(" ")).map((_, 1)).groupByKey()
groupByKeyRDD.map(t => (t._1, t._2.sum)).collect() foreach println
println("------ GroupByKey ------")
/*
补充:groupByKey shouldn't use map side combine
because map side combine does not reduce the amount of data shuffled
and requires all map side data be inserted into a hash table,
leading to more objects in the old gen.
*/
//TODO 3 K-V聚合全家桶:aggregateByKey -- 根据 Key1==Key2 shuffle 聚合 V
//笔记1:AggregateByKey & ReduceByKey 区别1 ==> Scala.reduce & fold 区别,集合内,集合外元素(zeroV)的规约聚合
//笔记2:AggregateByKey & ReduceByKey 区别2 ==> AggregateByKey 分区内规则(预聚合)与分区间规则 可不相同,ReduceByKey必须相同
//笔记3:AggregateByKey.zeroValue 对每一个分区内 遇到的第一个 从未出现过的Key 进行运算。
/*
def aggregateByKey[U: ClassTag](zeroValue: U)
(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)]
def aggregateByKey[U: ClassTag](zeroValue: U, numPartitions: Int)
(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)]
def aggregateByKey[U: ClassTag](zeroValue: U, partitioner: Partitioner)
(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)]
combineByKeyWithClassTag[U]((v: V) => cleanedSeqOp(createZero(), v),
cleanedSeqOp, combOp, partitioner)
*/
sc.makeRDD(strs, 2).flatMap(_.split(" ")).map((_, 1)).aggregateByKey(0)(_ + _, _ + _).collect foreach println
println("------ aggregateByKey ------")
//TODO 4 K-V聚合全家桶:foldByKey -- 根据 Key1==Key2 shuffle 聚合 V
//笔记1:foldByKey 是 aggregateByKey 简化版本——分区内规则==分区间规则,相较 reduceByKey 仍是与集合外部元素zeroV 规约聚合
/*
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
def foldByKey(zeroValue: V, numPartitions: Int)(func: (V, V) => V): RDD[(K, V)]
def foldByKey(zeroValue: V,partitioner: Partitioner)(func: (V, V) => V): RDD[(K, V)]
combineByKeyWithClassTag[V]((v: V) => cleanedFunc(createZero(), v),cleanedFunc, cleanedFunc, partitioner)
*/
sc.makeRDD(strs).flatMap(_.split(" ")).map((_, 1)).foldByKey(0)(_ + _).collect().foreach(println)
println("------ foldByKey ------")
//TODO 5 K-V聚合全家桶:combineByKey -- 根据 Key1==Key2 shuffle 聚合 V
//笔记1:第一个参数:将在同一分区遇到的第一个Key的Value对象进行 map 结构转换
// 第二个参数:同一分区内,相同Key的Value对象的合并规则
// 第三个参数:不同分区打碎 shuffle 时的 合并规则
/*
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]
combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners)(null)
*/
sc.makeRDD(strs).flatMap(_.split(" ")).map((_, 1)).combineByKey(
(count: Int) => count,
(count: Int, value: Int) => count + value,
(count1: Int, count2: Int) => count1 + count2
).collect().foreach(println)
println("------ combineByKey ------")
//TODO 6 RDD单值聚合:groupBy 转换算子(12345同为转换算子)
//笔记1:groupBy 不能识别 (t1,t2) tuple形式参数,会被认为2个参数传递,可使用 tuple 变量,tuple._1
//笔记2:groupByKeyRDD: RDD[(String, Iterable[Int])]
// groupByRDD: RDD[(String, Iterable[(String, Int)])]
//函数实质:this.map(t => (cleanF(t), t)).groupByKey(p)
//1.模拟K-V
val groupByRDD1: RDD[(String, Iterable[(String, Int)])] = sc.makeRDD(strs).flatMap(_.split(" ")).map((_, 1)).groupBy(t => t._1)
groupByRDD1.map {
//1.Iterable.size
// case tuple => (tuple._1,tuple._2.size)
//2.Iterable[Int].sum
case (word, list) =>
val ints: Iterable[Int] = list.map(t => t._2)
(word, ints.sum)
}.collect().foreach(println)
//2.直接单值计算
val groupByRDD2: RDD[(String, Iterable[String])] = sc.makeRDD(strs).flatMap(_.split(" ")).groupBy(s=>s)
groupByRDD2.map{
case (str,list)=>{
(str,list.size)
}
}.foreach(println)
println("------ GroupBy ------")
//TODO 7 RDD单值聚合:aggregate 行动算子
//def aggregate[U : ClassTag](zeroValue: U)(seqOp: (U, T) => U,combOp: (U, U) => U): U
//“零值”,在两个函数中均会使用,在第一次只有一个数值的时候进行补位运算。
//聚合每个分区的元素,使用seqOp函数聚合一个分区内的相同元素,并返回与传入RDD:T不同的结果类型RDD:U
//使用combineOp函数来聚合各个分区的结果。这两个函数都是修改和返回它们的第一个参数,而不是创建新的U来避免内存分配
//def Scala.Map.foldLeft[B](z: B)(op: (B, A) => B): B
sc.makeRDD(strs).flatMap(_.split(" ")).aggregate(mutable.Map[String, Int]())(
(map, s) => {
map(s) = map.getOrElse(s, 0) + 1
map
},
(map1, map2) => {
map1.foldLeft(map2)((innerMap, kv) => {
innerMap(kv._1) = innerMap.getOrElse(kv._1, 0) + kv._2
innerMap
})
}).foreach(println)
println("------ Aggregate ------")
//TODO 8 RDD单值聚合:fold 行动算子
//def fold(zeroValue: T)(op: (T, T) => T): T
//笔记1:fold行动算子,接入的RDD类型决定了T类型,并且零值,分区内/间 operator 均为T类型,那么如果想规约操作,只能使用可变类
//def Scala.Map.foldLeft[B](z: B)(op: (B, A) => B): B
//笔记2:foldLeft,z为初值,一般为 被合并的map本体,op函数参数为:被合并map,foldLeft函数执行对象(Map)的kv==tuple
sc.makeRDD(strs).flatMap(_.split(" ")).map(s => mutable.Map(s -> 1)).fold(mutable.Map[String, Int]())(
(map1, map2) => {
map1.foldLeft(map2)(
(innerMap, kv) => {
innerMap(kv._1) = innerMap.getOrElse(kv._1, 0) + kv._2
innerMap
}
)
}
).foreach(println)
println("------ Fold ------")
//TODO 9 行动算子:countByKey
//笔记1:只有当Map结果很小时,才应该使用这个方法,因为整个映射都被加载到Driver的内存中。要处理非常大的结果,请考虑使用rdd。
// 函数实质:mapValues(_ => 1L).reduceByKey(_ + _).collect().toMap
val countByKeyMap: collection.Map[String, Long] = sc.makeRDD(strs).flatMap(_.split(" ")).map((_, 1)).countByKey()
countByKeyMap.foreach(println)
println("------ CountByKey ------")
//TODO 10 行动算子:countByValue
//笔记1:不需要 (k,v),直接对 String/T(集合) RDD 进行计数,但实质与countByKey还是差不多。
// 函数实质:map(value => (value, null)).countByKey()
//笔记2:只有当结果Map很小时,才应该使用这个方法,因为整个Map都被加载到驱动程序的内存中。要处理非常大的结果,请考虑使用
// rdd.map(x => (x, 1L)).reduceByKey (_ + _); 它返回一个RDD[T, Long],而不是一个Map
val splitRDD: RDD[String] = sc.makeRDD(strs).flatMap(_.split(" "))
val countByValueMap: collection.Map[String, Long] = splitRDD.countByValue()
countByValueMap.foreach(println)
println("------ CountByValue ------")
//TODO 11 自定义累加器
//笔记1:单值累加器,只可对RDD内的某一个Key进行sum累加计算
//注册自定义累加器
val myAccu = new MyAccumulator
sc.register(myAccu, "MyAccumulator")
sc.makeRDD(strs).flatMap(_.split(" ")).foreach(myAccu.add)
println(myAccu.value)
println("------ MyAccumulator ------")
//TODO 12 广播变量
}
}
/**
* abstract class AccumulatorV2[IN, OUT] extends Serializable
* 笔记1:泛型为,输入泛型,输出泛型
* 笔记2:三兄弟 isZero,copy,reset,用于创建各个分区/Task的 独立累加器
* * copyAcc = copy()
* * copyAcc.reset()
* * assert(copyAcc.isZero, "copyAndReset must return a zero value copy")
* 笔记3:merge方法是将other数据合并至自己
*/
class MyAccumulator extends AccumulatorV2[String, mutable.HashMap[String, Int]] {
private val map = new mutable.HashMap[String, Int]()
override def isZero: Boolean = map.isEmpty
override def copy(): AccumulatorV2[String, mutable.HashMap[String, Int]] = {
val newMap = new MyAccumulator()
map.synchronized(newMap.map ++= map)
newMap
}
override def reset(): Unit = map.clear()
//分区内每处理一条数据,就进行一次模式匹配累加至累加器
override def add(v: String): Unit = {
map.get(v) match {
//tuple 元组写法
//case Some(count)=>map += ((v,count+1))
case Some(count) => map.put(v, count + 1)
case None => map.put(v, 1)
}
}
//合并不同分区/Task的累加器
override def merge(other: AccumulatorV2[String, mutable.HashMap[String, Int]]): Unit = {
other.value.foldLeft(map)(
(innerMap, kv) => {
innerMap(kv._1) = innerMap.getOrElse(kv._1, 0) + kv._2
innerMap
}
)
}
override def value: mutable.HashMap[String, Int] = map
}