大数据Spark学习之旅第四篇

「这是我参与11月更文挑战的第39天,活动详情查看:2021最后一次更文挑战」。

一、RDD 转换算子

1、双 Value 类型

1.1、intersection

  1. 函数签名

    def intersection(other: RDD[T]): RDD[T]
    复制代码
  2. 函数说明

    对源 RDD 和参数 RDD 求交集后返回一个新的 RDD

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - 双Value类型 - intersection
      // 交集 要求两个数据源数据类型保持一致
    
      val rdd1 = sc.makeRDD(List(1, 2, 3, 4))
      val rdd2 = sc.makeRDD(List(3, 4, 5, 6))
    
      // 交集 : 【3,4】
      val rdd3: RDD[Int] = rdd1.intersection(rdd2)
      println(rdd3.collect().mkString(","))
    
      sc.stop()
    
    }
    复制代码
  3. 思考一个问题:如果两个 RDD 数据类型不一致怎么办?

1.2、union

  1. 函数签名

    def union(other: RDD[T]): RDD[T]
    复制代码
  2. 函数说明

    对源 RDD 和参数 RDD 求并集后返回一个新的 RDD

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - 双Value类型 - union
      // 并集 要求两个数据源数据类型保持一致
      // 拉链操作两个数据源的类型可以不一致
    
      val rdd1 = sc.makeRDD(List(1, 2, 3, 4))
      val rdd2 = sc.makeRDD(List(3, 4, 5, 6))
    
      // 并集 : 【1,2,3,4,3,4,5,6】
      val rdd4: RDD[Int] = rdd1.union(rdd2)
      println(rdd4.collect().mkString(","))
    
      sc.stop()
    
    }
    复制代码
  3. 思考一个问题:如果两个 RDD 数据类型不一致怎么办?

1.3、subtract

  1. 函数签名

    def subtract(other: RDD[T]): RDD[T]
    复制代码
  2. 函数说明

    以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来。求差集

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - 双Value类型 - subtract
      // 差集 要求两个数据源数据类型保持一致
    
      val rdd1 = sc.makeRDD(List(1, 2, 3, 4))
      val rdd2 = sc.makeRDD(List(3, 4, 5, 6))
    
      // 差集 : 【1,2】
      val rdd5: RDD[Int] = rdd1.subtract(rdd2)
      println(rdd5.collect().mkString(","))
    
      sc.stop()
    
    }
    复制代码
  3. 思考一个问题:如果两个 RDD 数据类型不一致怎么办?

1.4、zip

  1. 函数签名

    def zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]
    复制代码
  2. 函数说明

    将两个 RDD 中的元素,以键值对的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD中的元素,Value 为第 2 个 RDD 中的相同位置的元素。

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - 双Value类型 - zip
      // 交集,并集和差集要求两个数据源数据类型保持一致
      // 拉链操作两个数据源的类型可以不一致
    
      val rdd1 = sc.makeRDD(List(1, 2, 3, 4))
      val rdd2 = sc.makeRDD(List(3, 4, 5, 6))
      val rdd7 = sc.makeRDD(List("3", "4", "5", "6"))
    
      // 拉链 : 【1-3,2-4,3-5,4-6】
      val rdd6: RDD[(Int, Int)] = rdd1.zip(rdd2)
      println(rdd6.collect().mkString(","))
    
      val rdd8 = rdd1.zip(rdd7)
      println(rdd8.collect().mkString(","))
    
      sc.stop()
    
    }
    复制代码
  3. 思考一个问题:如果两个 RDD 数据类型不一致怎么办?

    不一致也可以!

  4. 思考一个问题:如果两个 RDD 数据分区不一致怎么办?

    两个数据源要求分区数量要保持一致,分区数量不一致则报错!!!

  5. 思考一个问题:如果两个 RDD 分区数据数量不一致怎么办?

    两个数据源要求分区中数据数量保持一致,分区中的数据量不一致则报错!!!

2、Key - Value 类型

2.1、partitionBy

  1. 函数签名

    def partitionBy(partitioner: Partitioner): RDD[(K, V)]
    复制代码
  2. 函数说明

    将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitioner

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - (Key - Value类型) - partitionBy
      val rdd = sc.makeRDD(List(1, 2, 3, 4), 2)
    
      val mapRDD: RDD[(Int, Int)] = rdd.map((_, 1))
      // RDD => PairRDDFunctions
      // 隐式转换(二次编译)
    
      // partitionBy根据指定的分区规则对数据进行重分区
      val newRDD = mapRDD.partitionBy(new HashPartitioner(2))
    
      newRDD.saveAsTextFile("output")
    
      sc.stop()
    
    }
    复制代码
  3. 思考一个问题:如果重分区的分区器和当前 RDD 的分区器一样怎么办?

  4. 思考一个问题:Spark 还有其他分区器吗?

  5. 思考一个问题:如果想按照自己的方法进行数据分区怎么办?

2.2、reduceByKey

  1. 函数签名

    def reduceByKey(func: (V, V) => V): RDD[(K, V)]
    
    def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
    复制代码
  2. 函数说明

    可以将数据按照相同的 Key 对 Value 进行聚合

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - (Key - Value类型) - reduceByKey
      val rdd = sc.makeRDD(List(
        ("a", 1), ("a", 2), ("a", 3), ("b", 4)
      ))
    
      // reduceByKey : 相同的key的数据进行value数据的聚合操作
      // scala语言中一般的聚合操作都是两两聚合,spark基于scala开发的,所以它的聚合也是两两聚合
      // 【1,2,3】
      // 【3,3】
      // 【6】
      // reduceByKey中如果key的数据只有一个,是不会参与运算的。
      val reduceRDD: RDD[(String, Int)] = rdd.reduceByKey((x: Int, y: Int) => {
        println(s"x = ${x}, y = ${y}")
        x + y
      })
    
      reduceRDD.collect().foreach(println)
    
      sc.stop()
    
    }
    复制代码
  3. 小功能:WordCount

2.3、groupByKey

  1. 函数签名

    def groupByKey(): RDD[(K, Iterable[V])]
    
    def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
    
    def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
    复制代码
  2. 函数说明

    将数据源的数据根据 key 对 value 进行分组

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - (Key - Value类型) - groupByKey
      val rdd = sc.makeRDD(List(
        ("a", 1), ("a", 2), ("a", 3), ("b", 4)
      ))
    
      // groupByKey : 将数据源中的数据,相同key的数据分在一个组中,形成一个对偶元组
      //              元组中的第一个元素就是key,
      //              元组中的第二个元素就是相同key的value的集合
      val groupRDD: RDD[(String, Iterable[Int])] = rdd.groupByKey()
    
      groupRDD.collect().foreach(println)
    
      val groupRDD1: RDD[(String, Iterable[(String, Int)])] = rdd.groupBy(_._1)
      groupRDD1.collect().foreach(println)
    
      sc.stop()
    
    }
    复制代码
  3. 思考一个问题:reduceByKey 和 groupByKey 的区别?

    从 shuffle 的角度: reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是 reduceByKey可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的数据量,而 groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较高。

    从功能的角度: reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那么还是只能使用 groupByKey

2.4、aggregateByKey

  1. 函数签名

    def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,
        combOp: (U, U) => U): RDD[(K, U)]
    复制代码
  2. 函数说明

    将数据根据不同的规则进行分区内计算和分区间计算

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - (Key - Value类型) - aggregateByKey
    
      val rdd = sc.makeRDD(List(
        ("a", 1), ("a", 2), ("a", 3), ("a", 4)
      ), 2)
      // (a,【1,2】), (a, 【3,4】)
      // (a, 2), (a, 4)
      // (a, 6)
    
      // aggregateByKey存在函数柯里化,有两个参数列表
      // 第一个参数列表,需要传递一个参数,表示为初始值
      //       主要用于当碰见第一个key的时候,和value进行分区内计算
      // 第二个参数列表需要传递2个参数
      //      第一个参数表示分区内计算规则
      //      第二个参数表示分区间计算规则
    
      // math.min(x, y)
      // math.max(x, y)
      rdd.aggregateByKey(0)(
        (x, y) => math.max(x, y),
        (x, y) => x + y
      ).collect.foreach(println)
    
      sc.stop()
    
    }
    复制代码
    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - (Key - Value类型) - aggregateByKey
    
      val rdd = sc.makeRDD(List(
        ("a", 1), ("a", 2), ("b", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2)
    
      // aggregateByKey存在函数柯里化,有两个参数列表
      // 第一个参数列表,需要传递一个参数,表示为初始值
      //       主要用于当碰见第一个key的时候,和value进行分区内计算
      // 第二个参数列表需要传递2个参数
      //      第一个参数表示分区内计算规则
      //      第二个参数表示分区间计算规则
    
      // math.min(x, y)
      // math.max(x, y)
      rdd.aggregateByKey(5)(
        (x, y) => math.max(x, y),
        (x, y) => x + y
      ).collect.foreach(println)
    
      rdd.aggregateByKey(0)(
        (x, y) => x + y,
        (x, y) => x + y
      ).collect.foreach(println)
    
      rdd.aggregateByKey(0)(_ + _, _ + _).collect.foreach(println)
    
      sc.stop()
    
    }
    复制代码
  3. 思考一个问题:分区内计算规则和分区间计算规则相同怎么办?(WordCount)

    如果聚合计算时,分区内和分区间计算规则相同,spark提供了简化的方法:foldByKey

2.5、foldByKey

  1. 函数签名
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
复制代码
  1. 函数说明

    当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - (Key - Value类型) - foldByKey
    
      val rdd = sc.makeRDD(List(
        ("a", 1), ("a", 2), ("b", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2)
    
      //rdd.aggregateByKey(0)(_+_, _+_).collect.foreach(println)
    
      // 如果聚合计算时,分区内和分区间计算规则相同,spark提供了简化的方法
      rdd.foldByKey(0)(_ + _).collect.foreach(println)
    
      sc.stop()
    
    }
    复制代码

2.6、combineByKey

  1. 函数签名
def combineByKey[C](
    createCombiner: V => C,
    mergeValue: (C, V) => C,
    mergeCombiners: (C, C) => C): RDD[(K, C)]
复制代码
  1. 函数说明

    最通用的对 key-value 型 rdd 进行聚集操作的聚集函数(aggregation function)。类似于aggregate(),combineByKey()允许用户返回值的类型与输入不一致。

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - (Key - Value类型) - combineByKey
    
      val rdd = sc.makeRDD(List(
        ("a", 1), ("a", 2), ("b", 3),
        ("b", 4), ("b", 5), ("a", 6)
      ), 2)
    
      // combineByKey : 方法需要三个参数
      // 第一个参数表示:将相同key的第一个数据进行结构的转换,实现操作
      // 第二个参数表示:分区内的计算规则
      // 第三个参数表示:分区间的计算规则
      val newRDD: RDD[(String, (Int, Int))] = rdd.combineByKey(
        v => (v, 1),
        (t: (Int, Int), v) => {
          (t._1 + v, t._2 + 1)
        },
        (t1: (Int, Int), t2: (Int, Int)) => {
          (t1._1 + t2._1, t1._2 + t2._2)
        }
      )
    
      val resultRDD: RDD[(String, Int)] = newRDD.mapValues {
        case (num, cnt) => {
          num / cnt
        }
      }
      resultRDD.collect().foreach(println)
    
      sc.stop()
    
    }
    复制代码
  2. 思考一个问题:reduceByKey、foldByKey、aggregateByKey、combineByKey 的区别?

    • reduceByKey:相同 key 的第一个数据不进行任何计算,分区内和分区间计算规则相同

    • FoldByKey:相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则相同

    • AggregateByKey:相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则可以不相同

    • CombineByKey:当计算时,发现数据结构不满足要求时,可以让第一个数据转换结构。分区内和分区间计算规则不相同。

2.7、sortByKey

  1. 函数签名

    def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
    : RDD[(K, V)]
    复制代码
  2. 函数说明

    在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口(特质),返回一个按照 key 进行排序的

2.8、join

  1. 函数签名

    def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
    复制代码
  2. 函数说明

    在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素连接在一起的(K,(V,W))的 RDD

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - join
    
      val rdd1 = sc.makeRDD(List(
        ("a", 1), ("b", 2), ("c", 3)
      ))
    
      val rdd2 = sc.makeRDD(List(
        ("a", 5), ("b", 6), ("c", 4)
      ))
    
      // join : 两个不同数据源的数据,相同的key的value会连接在一起,形成元组
      //        如果两个数据源中key没有匹配上,那么数据不会出现在结果中
      //        如果两个数据源中key有多个相同的,会依次匹配,可能会出现笛卡尔乘积,数据量会几何性增长,会导致性能降低。
      val joinRDD: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
    
      joinRDD.collect().foreach(println)
    
      sc.stop()
    
    }
    复制代码

2.9、leftOuterJoin or rightOuterJoin

  1. 函数签名

    def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]
    复制代码
  2. 函数说明

    类似于 SQL 语句的左外连接、右外连接

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - leftOuterJoin or rightOuterJoin
    
      val rdd1 = sc.makeRDD(List(
        ("a", 1), ("b", 2), ("c", 3)
      ))
    
      val rdd2 = sc.makeRDD(List(
        ("a", 4), ("b", 5) //, ("c", 6)
      ))
      val leftJoinRDD = rdd1.leftOuterJoin(rdd2)
      val rightJoinRDD = rdd1.rightOuterJoin(rdd2)
    
      leftJoinRDD.collect().foreach(println)
      //    rightJoinRDD.collect().foreach(println)
    
      sc.stop()
    
    }
    复制代码

2.10、cogroup

  1. 函数签名

    def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
    复制代码
  2. 函数说明

    在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD

    def main(args: Array[String]): Unit = {
    
      val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
      val sc = new SparkContext(sparkConf)
    
      // 算子 - cogroup
    
      val rdd1 = sc.makeRDD(List(
        ("a", 1), ("b", 2) //, ("c", 3)
      ))
    
      val rdd2 = sc.makeRDD(List(
        ("a", 4), ("b", 5), ("c", 6), ("c", 7)
      ))
    
      // cogroup : connect + group (分组,连接)
      val cgRDD: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
    
      cgRDD.collect().foreach(println)
    
      sc.stop()
    
    }
    复制代码

二、友情链接

大数据Spark学习之旅第三篇

大数据Spark学习之旅第二篇

大数据Spark学习之旅第一篇

猜你喜欢

转载自juejin.im/post/7039367828180303902
今日推荐