【Spark 2.1.1 基础总结】11种 Spark WordCount 方法 2019_12_10

总结

一、5种 k-v聚合(转换算子)

相同底层函数combineByKeyWithClassTag
reduceByKey,groupByKey,aggregateByKey,foldByKey,combineByKey

集合外元素概念*2
aggregateByKey 定制化程度最高,3参数为:零值(集合外元素),分区内数据合并方法,不同分区间聚合结果合并方法
foldByKeyaggregateByKey 的简化版/参数严格版 均与来源RDD类型相同,2参数为:零值(集合外元素),分区内==分区间

只·集合内元素*2
combineByKey 只在集合内部做聚合,没有集合外元素概念,对遇到的第一个Key的Value对象进行结构转换。3参数
reduceByKeycombineByKey 的简化版,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
}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值