spark 常用算子

304 阅读15分钟

1、转换算子

1.1 map
val sc = SparkContext.getOrCreate(new SparkConf().setMaster("local[2]").setAppName(this.getClass.getSimpleName))
    val a1 = sc.makeRDD(List(2, 4, 5, 7, 8, 9), 2)
    a1.map(x => {
      println(">>>>>>>>>>>")
      x * 2
    }).collect.foreach(println(_))
1.2 mapPartitions
  • map和mapPartition的区别:
  • map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。是串行,效率低
  • mapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,所以可以增加或减少数据。并行,效率高,但消耗内存,容易造成内存溢出
// 1.每个分区计算一次的测试
    println("每个分区计算一次")
    a1.mapPartitions(x => {
      println(">>>>>>>>>>>")
      x.map(_ * 2)
    }).foreach(println)
    //2.返回每个分区的最大值
    println("返回每个分区的最大值")
    sc.makeRDD(List(1, 2, 3, 4)).mapPartitions(x => {
      List(x.max).toIterator
    }).collect.foreach(println)

    //3、将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据。
    println("过滤数据")
    a1.mapPartitions(x => x.filter(_ == 2)).foreach(println(_))
1.3 mapPartitionsWithIndex
  • 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
  • 在使用中,前一个值为分区号,后一个值为分区的迭代器
//只保留分区号为1的分区数据
    println("只保留分区号为1的分区数据")
    sc.makeRDD(List(1, 2, 3, 4), 2).mapPartitionsWithIndex((x, y) => {
      if (x == 1) {
        y
      } else {
        Nil.toIterator
      }
    }).collect.foreach(println)
    //输出对应分区号
    println("输出对应分区号")
    a1.mapPartitionsWithIndex({
      case (x, y) =>
        y.map((_, "分区号为:" + x))
    }).foreach(println(_))
    println()
1.4 flatMap

将处理的数据进行扁平化后再进行映射处理

println("扁平映射1")
sc.makeRDD(List(
List(1, 2), List(3, 4)
    )).flatMap(x => x.map((_, 1))).collect.foreach(println)
    println("扁平映射2")
    sc.makeRDD(List("aaa bbb", "ccc ddd", "ccc eee")).flatMap(x => {
      x.split(" ")
    }).map((_, 1)).reduceByKey(_ + _).foreach(println(_))
1.5 glom

将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变

println("同分区的所有数据转为数组")
    sc.makeRDD(List(1, 2, 3, 4, "a"), 2).glom().collect.foreach(x => {
      println(x.mkString(","))
    })
    println("计算所有分区最大值求和(分区内取最大值,分区间最大值求和)")
    val max = sc.makeRDD(List(1, 2, 3, 4), 2).glom().map(x => x.max).collect.sum
    println(max)
1.6 groupBy

​ 将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为 shuffle。极限情况下,数据可能被分在同一个分区中一个组的数据在一个分区中,但是并不是说一个分区中只有一个组

println("根据首字母进行分组")
    sc.makeRDD(List("hello", "hadoop", "spark", "hive")).groupBy(_.charAt(0)).collect.foreach(println)
    println("根据时段进行分组")
    sc.makeRDD(List("81.220.24.207 - - 17/05/2015:10:05:21 +0000 GET /favicon.ico"
      , "66.249.73.135 - - 17/05/2015:11:05:17 +0000 GET /blog/geekery/vmware-cpu-performance.html"
      , "46.105.14.53 - - 17/05/2015:11:05:42 +0000 GET /blog/tags/puppet?flav=rss20"
      , "218.30.103.62 - - 17/05/2015:11:05:11 +0000 GET /robots.txt")).map(x => {
      val datas = x.split(" ")
      val time = datas(3)
      val sdf = new SimpleDateFormat("dd/MM/yy:HH:mm:ss")
      val date = sdf.parse(time)
      val sdf2 = new SimpleDateFormat("HH")
      val hour = sdf2.format(date)
      (hour, 1)
    }).groupBy(_._1).map {
      case (hour, iter) => (hour, iter.size)
    }.collect.foreach(println(_))
    //      .mapValues(_.size).foreach(println(_))
1.7 filter
  • 将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
  • 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
sc.makeRDD(List("81.220.24.207 - - 17/04/2015:10:05:21 +0000 GET /favicon.ico"
      , "66.249.73.135 - - 17/05/2015:11:05:17 +0000 GET /blog/geekery/vmware-cpu-performance.html"
      , "46.105.14.53 - - 17/05/2015:11:05:42 +0000 GET /blog/tags/puppet?flav=rss20"
      , "218.30.103.62 - - 17/05/2015:11:05:11 +0000 GET /robots.txt")).filter(x => {
      val datas = x.split(" ")
      val time = datas(3)
      time.startsWith("17/05/2015")
    }).collect.foreach(println(_))
1.8 sample

根据指定的规则从数据集中抽取数据

1.8.1 抽取数据不放回(伯努利算法)
  • 伯努利算法:又叫 0、1 分布。例如扔硬币,要么正面,要么反面。 具体实现:根据种子和随机算法算出一个数和第二个参数设置几率比较,小于第二个参数要,大于不要
  • 第一个参数:抽取的数据是否放回,false:不放回
  • 第二个参数:抽取的几率,范围在[0,1]之间,0:全不取;1:全取;
  • 第三个参数:随机数种子,如果不写该参数,那么使用的是当前系统时间作为随机数种子
1.8.2 抽取数据放回(泊松算法)
  • 第一个参数:抽取的数据是否放回,true:放回;false:不放回
  • 第二个参数:重复数据的几率,范围大于等于 0.表示每一个元素被期望抽取到的次数
  • 第三个参数:随机数种子,如果不写该参数,那么使用的是当前系统时间作为随机数种子
val dataRDD = sc.makeRDD(List(
      1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    ), 1)
    //抽取数据不放回
    dataRDD.sample(false, 0.5).foreach(x => print(x + "\t"))
    println()
    //抽取数据放回
    dataRDD.sample(true, 2).foreach(x => print(x + "\t"))
    println()
1.9 distinct

将数据集中重复的数据去重

distinctList底层是个HashSet distinctRDD底层方法如下

map(x => (x, null)).reduceByKey((x, ) => x, numPartitions).map(._1)
val str = sc.makeRDD(List(1, 2, 3, 4, 1, 2, 3, 4)).distinct.collect.mkString(",")
    println(str)
1.10 coalesce

根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率 当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本 coalesce方法默认不会打乱分区重新组合,容易导致数据倾斜 但coalease还有第二个参数,默认为false,不shuffle,设置为true即可进行shuffle让数据变的均衡

//缩减分区用coalease+shuffle

    sc.makeRDD(List(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 8, 6), 6).coalesce(2)
      .mapPartitions(x => x.filter(_ > 0)).glom().map(x => x.sum).collect.foreach(x => println(x))
1.11 repartition

该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。无论是将分区数多的 RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition 操作都可以完成,因为无论如何都会经 shuffle 过程。

//扩大分区用reparation默认是shuffle

    sc.makeRDD(List(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 8, 6), 2).repartition(4)
      .mapPartitions(x => x.filter(_ > 0)).glom().map(x => x.sum).collect.foreach(x => println(x))
1.12 sortBy
  • 该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一致。中间存在 shuffle 的过程。
  • 可以有第二个参数,Boolean值,倒序
val functionToString = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 8, 6)).map(x => x).sortBy(x => x).collect().mkString(",")
    val functionToString2 = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 8, 6)).map(x => x).sortBy(x => x, false).collect().mkString(",")
    println(functionToString)
    println(functionToString2)
1.13 intersection

对源 RDD 和参数 RDD 求交集后返回一个新的 RDD,要求类型相同

sc.makeRDD(List(1, 2, 3, 4)).intersection(sc.makeRDD(List(3, 4, 5, 6))).foreach(println(_))
1.14 union

对原 RDD 和参数 RDD 求并集后返回一个新的 RDD,要求类型相同

sc.makeRDD(List(1, 2, 3, 4)).union(sc.makeRDD(List(3, 4, 5, 6))).foreach(println(_))
1.15 subtract

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

sc.makeRDD(List(1, 2, 3, 4)).subtract(sc.makeRDD(List(3, 4, 5, 6))).foreach(println(_))
1.16 zip

将两个 RDD 中的元素,以键值对(元组)的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD中的元素,Value 为第 2 个 RDD 中的相同位置的元素。要求拉链分区数量相同,分区数据长度相同

sc.makeRDD(List(1, 2, 3, 4)).zip(sc.makeRDD(List(3, 4, 5, 6))).foreach(println(_))
1.17 partitionBy

将数据按照指定 Partitioner 重新进行分区。隐式转换为PairRDDFunctions,二次编译。 区别于coalease和repartition是数量上的改变,二partitionBy是功能上的改变,比如说默认的分区器是 HashPartitioner,或按照key的奇偶分区等规则

  • 1、如果重分区的分区器和当前RDD 的分区器一样怎么办?
  • 源码方法里类型相同,数量相等,则会认为是同一个分区器,返回自己不会产生新的RDD
  • 2、Spark 还有其他分区器吗?
  • 三个,Hash和Range(排序时用)和Python(private)
  • 3、如果想按照自己的方法进行数据分区怎么办?
  • 自定义
println(sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3).partitionBy(new HashPartitioner(2)).getNumPartitions)
    println(sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3).getNumPartitions)
1.18 reduceByKey

可以将数据按照相同的 Key 对 Value 进行聚合,和scala语言中一样,都是两两聚合。如果Key只有一个,只返回不计算。还可以指定分区task数

sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3), ("a", 4), ("b", 5), ("c", 6))).reduceByKey(_ + _).foreach(println(_))
    println("-------分割线-----------")
    sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3), ("a", 4), ("b", 5), ("c", 6))).reduceByKey(_ + _, 2).foreach(println(_))
1.19 groupByKey

将数据源的数据根据 key 对 value 进行分组,形成一个对偶元组,元组的第二个元素是value,groupBy分组后的元组第二个元素是KV

println(sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)), 3).groupByKey().getNumPartitions)
    sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)), 3).groupByKey(2).foreach(x => x._2.foreach(y => println((x._1, y))))
    sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)), 3).groupByKey(new HashPartitioner(2)).foreach(x => x._2.foreach(y => println((x._1, y))))

image.png

思考:reduceByKey 和 groupByKey 的区别?

  • 从 shuffle 的角度:reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是 reduceByKey可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的数据量,而 groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较高。
  • 从功能的角度:reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那么还是只能使用 groupByKey
1.20 aggregateByKey

​ reduceByKey分区内和分区之间的计算规则是相同的,而aggregateByKey是将数据根据不同的规则分别进行不同的分区内计算和分区间计算,规则分别设立。aggregateByKey的返回值和初始化值必须相同(最终结果的类型和初始值一致)

​ aggregateByKey 算子是函数柯里化,存在两个参数列表:

  • 第一个参数列表中的参数表示初始值
  • 第二个参数列表中含有两个参数
  • ​ 2.1 第一个参数表示分区内的计算规则
  • ​ 2.2 第二个参数表示分区间的计算规则
sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("b", 4))).aggregateByKey(0)(
      (x, y) => math.max(x, y),
      (x, y) => x + y
    ).collect.foreach(println(_))
    println("求相同key的平均值")
    val newRDD = sc.makeRDD(List(("a", 88), ("b", 95), ("a", 91), ("b", 93),
      ("a", 95), ("b", 98)), 2).aggregateByKey((0, 0))(
      (x, y) => {
        (x._1 + y, x._2 + 1)
      },
      (x1, x2) => {
        (x1._1 + x2._1, x1._2 + x2._2)
      }
    )
    newRDD.mapValues {
      case (num, count) => num / count
    }.foreach(println(_))

image.png

1.21 foldByKey

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

sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3))).foldByKey(0)(_ + _).foreach(println(_))

image.png

1.22 combineByKey

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

combineByKey : 方法需要三个参数

  • 第一个参数表示:将相同key的第一个数据进行结构的转换,实现操作
  • 第二个参数表示:分区内的计算规则
  • 第三个参数表示:分区间的计算规则
val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93),
      ("a", 95), ("b", 98))
    val input: RDD[(String, Int)] = sc.makeRDD(list, 2)
    //acc: (Int, Int)数据类型要动态识别
    val combineRdd: RDD[(String, (Int, Int))] = input.combineByKey(
      (_, 1),
      (acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),
      (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
    )
    combineRdd.mapValues {
      case (num, count) => num / count
    }.foreach(println(_))

image.png

思考:reduceByKey、foldByKey、aggregateByKey、combineByKey 的区别?

四者源码如下:

reduceByKey:
      combineByKeyWithClassTag[V](
             (v: V) => v, // 第一个值不会参与计算
             func, // 分区内计算规则
             func, // 分区间计算规则
             partitioner
             )

    aggregateByKey :
      combineByKeyWithClassTag[U](
            (v: V) => cleanedSeqOp(createZero(), v), // 初始值和第一个key的value值进行的分区内数据操作
            cleanedSeqOp, // 分区内计算规则
            combOp,       // 分区间计算规则
            partitioner
            )

    foldByKey:
      combineByKeyWithClassTag[V](
            (v: V) => cleanedFunc(createZero(), v), // 初始值和第一个key的value值进行的分区内数据操作
            cleanedFunc,  // 分区内计算规则
            cleanedFunc,  // 分区间计算规则
            partitioner
            )

    combineByKey :
      combineByKeyWithClassTag(
            createCombiner,  // 相同key的第一条数据进行的处理函数
            mergeValue,      // 表示分区内数据的处理函数
            mergeCombiners,  // 表示分区间数据的处理函数
            partitioner
            )

以WordCount为例的简单解释

  • reduceByKey: 相同 key 的第一个数据不进行任何计算,分区内和分区间计算规则相同
  • FoldByKey: 相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则相同
  • AggregateByKey:相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则可以不相同
  • CombineByKey:当计算时,发现数据结构不满足要求时,可以让第一个数据转换结构。分区内和分区间计算规则不相同。
rdd.reduceByKey(_+_) rdd.aggregateByKey(0)(_+_, _+_) rdd.foldByKey(0)(_+_) rdd.combineByKey(v=>v,(x:Int,y)=>x+y,(x:Int,y:Int)=>x+y)
1.23 sortByKey

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

val dataRDD1 = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
    dataRDD1.sortByKey(true).collect.foreach(println(_))
    dataRDD1.sortByKey(false).foreach(println(_))

image.png

1.24 join

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

​ Key类型应该是相同的,如果两个数据源中的key没有匹配上,那么数据不会出现在结果中;如果两个数据源中的key有多个相同的,会依次匹配,出现笛卡尔积,对性能有影响

val rdd3: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (2, "b"), (3, "c")))
    val rdd4: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (3, 6)))
    rdd3.join(rdd4).collect().foreach(println)

image.png

1.25&1.26 leftOuterJoin&rightOuterJoin
val dr1 = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
    val dr2 = sc.makeRDD(List(("e", 1), ("b", 2), ("c", 3)))
    println("leftOuterJoin")
    dr1.leftOuterJoin(dr2).collect().foreach(println)
    println("rightOuterJoin")
    dr1.rightOuterJoin(dr2).collect().foreach(println)

image.png

1.27 cogroup

cogroup=connect+group,分组连接,有点像根据重复K拉平后的join操作

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

val cgRDD1 = sc.makeRDD(List(("a", 1), ("a", 2), ("c", 3)))
    val cgRDD2 = sc.makeRDD(List(("a", 1), ("c", 2), ("c", 3), ("d", 3)))
    cgRDD1.cogroup(cgRDD2).collect.foreach(println)

image.png

2、行动算子

2.1 reduce

聚集RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据

val r1 = sc.makeRDD(List(1, 2, 3)).reduce(_ + _)
println(r1)

image.png

2.2 collect

在驱动程序中,以数组Array 的形式返回数据集的所有元素

val ints11: Array[Int] = sc.makeRDD(List(4,2,3,1)).collect()
    println(ints11.mkString(","))

image.png

2.3 count

返回RDD 中元素的个数

val cnt =  sc.makeRDD(List(4,2,3,1)).count()
println(cnt)

image.png

2.4 first

返回RDD 中的第一个元素

val first = sc.makeRDD(List(4,2,3,1)).first()
println(first)

image.png

2.5 take

返回一个由RDD 的前 n 个元素组成的数组

val ints: Array[Int] = sc.makeRDD(List(4,2,3,1)).take(3)
println(ints.mkString(","))

image.png

2.6 takeOrdered

返回该 RDD 排序后的前 n 个元素组成的数组

val rddt = sc.makeRDD(List(4,2,3,1))
val ints1: Array[Int] = rddt.takeOrdered(3)
println(ints1.mkString(","))

image.png

2.7 aggregate

​ 分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合

  • aggregate:初始值在参与分区内计算后,再参与分区间的计算
  • aggregateByKey:初始值仅参与分区内计算,不参与分区间的计算
val i = sc.makeRDD(List(1, 2, 3, 4), 2).aggregate(10)(_ + _, _ + _)
println(i)

image.png

2.8 fold

折叠操作,aggregate 的简化版操作

println(sc.makeRDD(List(1, 2, 3, 4), 2).fold(10)(_ + _))

image.png

2.9 countByKey

统计每种 key 的个数

sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b",2))).countByKey().foreach(println)

image.png

2.10 countByValue

用于没有key的单值情况,如果有key则把KV看作组合的单值

sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b",2))).countByValue().foreach(println)

image.png

思考:WordCount有多少种RDD实现方法

  1. groupBy+mapValues
  2. map+groupByKey+mapValues
  3. map+reduceByKey
  4. map+aggregateByKey
  5. map+foldByKey
  6. map+combineByKey
  7. map+countByKey
  8. countByValue
  9. map+reduce
  10. map+fold
  11. map+aggregate
val rdd = sc.makeRDD(List("hello scala", "hello spark"))
    val words = rdd.flatMap(_.split(" "))
    println("第1种")
    words.groupBy(word => word).mapValues(_.size).foreach(println)
    println("第2种")
    words.map((_, 1)).groupByKey().mapValues(_.size).foreach(println)
    println("第3种")
    words.map((_, 1)).reduceByKey(_ + _).foreach(println)
    println("第4种")
    words.map((_, 1)).aggregateByKey(0)(_ + _, _ + _).foreach(println)
    println("第5种")
    words.map((_, 1)).foldByKey(0)(_ + _).foreach(println)
    println("第6种")
    words.map((_, 1)).combineByKey(v => v, (x: Int, y) => x + y, (x: Int, y: Int) => x + y).foreach(println)
    println("第7种")
    words.map((_, 1)).countByKey().foreach(println)
    println("第8种")
    words.countByValue().foreach(println)
    println("第9种")
    import scala.collection.mutable
    words.map(
      word => {
        mutable.Map[String, Long]((word,1))
      }
    ).reduce(
      (x,y)=>{
        y.foreach{
          case (word,count)=>{
            val newCount = x.getOrElse(word,0L)+count
            x.update(word,newCount)
          }
        }
        x
      }
    ).foreach(println)
    println("第10种")
    words.map(
      x=>{
        mutable.Map[String,Long]((x,1))
      }
    ).fold(mutable.Map[String,Long]())(
      (x,y)=>{
        y.foreach{
          case (word,count)=>{
            val newCount = x.getOrElse(word,0L)+count
            x.update(word,newCount)
        }
        }
        x
      }
    ).foreach(println)
    println("第11种")
    words.map(
      x=>{
        mutable.Map[String,Long]((x,1))
      }
    ).aggregate(mutable.Map[String,Long]())(
      (x,y)=>{
        y.foreach{
          case (word,count)=>{
            val newCount = x.getOrElse(word,0L)+count
            x.update(word,newCount)
          }
        }
        x
      },
      (x,y)=>{
        y.foreach{
          case (word,count)=>{
            val newCount = x.getOrElse(word,0L)+count
            x.update(word,newCount)
          }
        }
        x
      }
    ).foreach(println(_))
2.11 saveAsTextFile、saveAsObjectFile、saveAsSequenceFile

将数据保存到不同格式的文件中。文件目录不能已存在。saveAsSequenceFile要求必须是KV键值类型

sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b", 2))).saveAsTextFile("files/output/04saveAsTextFile")
sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b", 2))).saveAsObjectFile("files/output/05saveAsObjectFile")
sc.makeRDD(List(("a", 1), ("a", 5), ("b", 2), ("b", 2))).saveAsSequenceFile("files/output/06saveAsSequenceFile")
2.14 foreach

分布式遍历RDD 中的每一个元素,调用指定函数

collect以分区为单位采集回到driver端再通过foreach打印,foreach是在executor端分布式采集打印

算子 : Operator(操作) RDD的方法和Scala集合对象的方法不一样 集合对象的方法都是在同一个节点的内存中完成的。 RDD的方法可以将计算逻辑发送到Executor端(分布式节点)执行 为了区分不同的处理效果,所以将RDD的方法称之为算子。 RDD的方法外部的操作都是在Driver端执行的,而方法内部的逻辑代码是在Executor端执行。

println("collect.foreach")
sc.makeRDD(List(1,2,3,4),2).collect.foreach(println)
println("直接foreach")
sc.makeRDD(List(1,2,3,4),2).foreach(println)

3. 需求分析

agent.log:时间戳,省份,城市,用户,广告,中间字段使用空格分隔

需求描述

统计出每一个省份每个广告被点击数量排行的 Top3

var dataRDD = sc.textFile("agent.log")
val mapRDD = dataRDD.map(
    line => {
        val = datas = line.split(",")
        ((datas(1),datas(4)),1)
    }
)
val reduceRDD = mapRDD.reduceByKey(_+_)
val newMapRDD = reduceRDD.map {
    case ((prv, ad), sum) => {
        (prv, (ad,sum))
    }
}
val groupRDD = newMapRDD.groupByKey()
val resultRDD = groupRDD.mapValues(
    iter => {
        iter.toList.sortBy(_.2)(Ordering.Int.reverse).take(3)
    }
)