用一个例子告诉你 spark中combineByKeyWithClassTag算子的执行原理

目录

1. 说明

2. 示例

场景1: 设置RDD分区数为2

场景2: 设置RDD分区数为5

3. 关于为什么key类型为Array时,不支持Map端合并


1. 说明

我们可以将RDD想象成一个分布式表,按照不同的分区,数据分布在不同的节点上
我们对RDD做分组聚合时,一般都先在每个分区内对相同key的value做一次聚合
                       再在分区间对相同key的value做一次聚合

combineByKeyWithClassTag,RDD聚合操作中最通用的函数,几乎能覆盖所有聚合操作的场景

def combineByKeyWithClassTag[C](
    createCombiner: V => C,         // 指定Lambda表达式,用于转换value的数据类型
    mergeValue: (C, V) => C,        // 指定Lambda表达式,用于分区内 合并相同key下的value值
    mergeCombiners: (C, C) => C,    // 指定Lambda表达式,用于分区内 合并相同key下的value值
    partitioner: Partitioner,       // 指定分区器
    mapSideCombine: Boolean = true, // 是否支持map端聚合
    serializer: Serializer = null)(implicit ct: ClassTag[C]): RDD[(K, C)]

createCombiner、mergeValue用于map端的聚合操作
mergeCombiners 用于reduce端的聚合操作

2. 示例

需求: 统计每个用户下,所拥有的的单词数量

场景1: 设置RDD分区数为2

  test("combineByKeyWithClassTag示例") {
    // 初始化 spark配置实例
    val sparkconf: SparkConf = new SparkConf().setMaster("local[2]").setAppName("")
    // 初始化 spark环境对象
    val sc: SparkContext = new SparkContext(sparkconf)

    val rdd: RDD[(String, String)] = sc.makeRDD(List(
        ("张飞", "张飞java scala spark")
      , ("张飞", "张飞java scala spark")
      , ("刘备", "刘备java spark")
      , ("刘备", "刘备java scala spark")
      , ("刘备", "刘备scala spark")
      , ("关羽", "关羽java scala spark")
      , ("关羽", "关羽java scala")
      , ("关羽", "关羽java scala spark")
      , ("关羽", "关羽java spark")))

    // 查看每个分区的内容
    val value1: RDD[(String, String)] = rdd.mapPartitionsWithIndex(
      (i, iter) => {
        println(s"分区编号$i :${iter.mkString(" ")}");
        iter
      }
    )
    value1.collect()

    /*
    * TODO 需求: 查询每个用户下,所拥有的的单词数量
    *
    * */
    val groupByName: RDD[(String, Int)] = rdd.combineByKeyWithClassTag(
      /*
      * Lambda1: 分区内用来将 String 转换成Int
      * */
      partionFirstValue => {
        println(s"Lambda1:$partionFirstValue");
        partionFirstValue.split(" ").length
      }
      /*
      * Lambda2: 分区内对相同key 做聚合操作
      * */
      , (key: Int, value: String) => {
        println(s"Lambda2:$key # $value")
        key + value.split(" ").length
      }
      /*
      * Lambda3: 分区间对相同key 做聚合操作
      * */
      , (v1: Int, v2: Int) => {
        println(s"Lambda3:$v1 # $v2")
        v1 + v2
      }
    )

    groupByName.collect().foreach(println(_))
    sc.stop()
  }

通过执行日志和结果来执行过程

//1.查看每个分区下的数据
分区编号1 :(刘备,刘备scala spark) (关羽,关羽java scala spark) (关羽,关羽java scala) (关羽,关羽java scala spark) (关羽,关羽java spark)
分区编号0 :(张飞,张飞java scala spark) (张飞,张飞java scala spark) (刘备,刘备java spark) (刘备,刘备java scala spark)

//2.分区内合并操作
分区编号0 :(张飞,张飞java scala spark) (张飞,张飞java scala spark) (刘备,刘备java spark) (刘备,刘备java scala spark)
    Lambda1:张飞java scala spark
        Lambda2:3 # 张飞java scala spark

    Lambda1:刘备scala spark
        Lambda2:2 # 刘备java scala spark

    分区编号0输出结果: (张飞,6)  (刘备,5) 

分区编号1 :(刘备,刘备scala spark) (关羽,关羽java scala spark) (关羽,关羽java scala) (关羽,关羽java scala spark) (关羽,关羽java spark)
    Lambda1:刘备java spark
        因为这个分区中,key=刘备 只有一个元素,所以不会调用 分区间合并函数

    Lambda1:关羽java scala spark
        Lambda2:3 # 关羽java scala
        Lambda2:5 # 关羽java scala spark
        Lambda2:8 # 关羽java spark

    分区编号1输出结果: (刘备,2) (关羽,10)  

//3.分区间合并操作
    拉取map端合并的结果: (张飞,6)  (刘备,5) (刘备,2) (关羽,10)  
        Lambda3:5 # 2
        只有 key=刘备 需要合并value,所以分区间合并函数 只被调用了一次

//4.输出最终计算结果
    (关羽,10)
    (张飞,6)
    (刘备,7)

场景2: 设置RDD分区数为5

        setMaster("local[5]") 用CPU核数为5来模拟

//1.查看每个分区下的数据
分区编号2 :(刘备,刘备java scala spark) (刘备,刘备scala spark)
分区编号1 :(张飞,张飞java scala spark) (刘备,刘备java spark)
分区编号0 :(张飞,张飞java scala spark)
分区编号3 :(关羽,关羽java scala spark) (关羽,关羽java scala)
分区编号4 :(关羽,关羽java scala spark) (关羽,关羽java spark)

//2.分区内合并操作
分区编号2 :(刘备,刘备java scala spark) (刘备,刘备scala spark)
    Lambda1:刘备java scala spark
        Lambda2:3 # 刘备scala spark

    分区编号2输出结果: (刘备,5)

分区编号1 :(张飞,张飞java scala spark) (刘备,刘备java spark)
    Lambda1:张飞java scala spark
    Lambda1:刘备java spark

    分区编号1输出结果: (张飞,3) (刘备,2)

分区编号0 :(张飞,张飞java scala spark)
    Lambda1:张飞java scala spark

    分区编号0输出结果: (张飞,3)

分区编号3 :(关羽,关羽java scala spark) (关羽,关羽java scala)
    Lambda1:关羽java scala spark
        Lambda2:3 # 关羽java scala

    分区编号3输出结果: (关羽,5)
  
分区编号4 :(关羽,关羽java scala spark) (关羽,关羽java spark)
    Lambda1:关羽java scala spark
        Lambda2:3 # 关羽java spark 

    分区编号4输出结果: (关羽,5)

//3.分区间合并操作
    拉取map端合并的结果: (刘备,5) (刘备,2) (张飞,3) (张飞,3) (关羽,5) (关羽,5)
    Lambda3:3 # 3   合并key=张飞 的value
    Lambda3:2 # 5   合并key=刘备 的value
    Lambda3:5 # 5   合并key=关羽 的value

//4.输出最终计算结果
    (张飞,6)
    (刘备,7)
    (关羽,10)

3. 关于为什么key类型为Array时,不支持Map端合并

  test("当key类型为Array时,combineByKeyWithClassTag示例") {
    // 初始化 spark配置实例
    val sparkconf: SparkConf = new SparkConf().setMaster("local[5]").setAppName("")
    // 初始化 spark环境对象
    val sc: SparkContext = new SparkContext(sparkconf)

    val rdd: RDD[(Array[String], String)] = sc.makeRDD(List(
      ("张飞", "张飞java scala spark")
      , ("张飞", "张飞java scala spark")
      , ("刘备", "刘备java spark")
      , ("刘备", "刘备java scala spark")
      , ("刘备", "刘备scala spark")
      , ("关羽", "关羽java scala spark")
      , ("关羽", "关羽java scala")
      , ("关羽", "关羽java scala spark")
      , ("关羽", "关羽java spark"))).map( tuple => (tuple._2.split(" "),tuple._1))


    /*
    * TODO 需求: 查询每个用户下,所拥有的的单词数量
    *
    * */
    val groupByName: RDD[(Array[String], Int)] = rdd.combineByKeyWithClassTag(
      /*
      * Lambda1: 分区内用来将 String 转换成Int
      * */
      partionFirstValue => {
        println(s"Lambda1:$partionFirstValue");
        partionFirstValue.split(" ").length
      }
      /*
      * Lambda2: 分区内对相同key 做聚合操作
      * */
      , (key: Int, value: String) => {
        println(s"Lambda2:$key # $value")
        key + value.split(" ").length
      }
      /*
      * Lambda3: 分区间对相同key 做聚合操作
      * */
      , (v1: Int, v2: Int) => {
        println(s"Lambda3:$v1 # $v2")
        v1 + v2
      }
      ,new HashPartitioner(5)
      ,false
    )

    groupByName.collect().foreach(println(_))
    sc.stop()
  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值