SparkCore之转换算子:RDD概述属性介绍,RDD特点,RDD编程模型,RDD的创建与转换, transformation转换算子Value类型与双value类型交互,key-value类型算子

文章目录

 

第1章 RDD概述

1.1 什么是RDD

1.2 RDD的属性

1.3 RDD特点

1.3.1 分区

1.3.2 只读

1.3.3 依赖

1.3.4 缓存

1.3.5 CheckPoint

第2章 RDD编程

2.1 编程模型

2.2 RDD的创建

2.2.1 从集合中创建

2.2.2 由外部存储系统的数据集创建

2.3 RDD的转换(面试开发重点)

2.3.1 Value类型

2.3.1.1 map(func)案例

2.3.1.2 mapPartitions(func) 案例

2.3.1.3 mapPartitionsWithIndex(func) 案例

2.3.1.4 flatMap(func) 案例

2.3.1.5 map()和mapPartition()的区别

2.3.1.6 glom案例

2.3.1.7 groupBy(func)案例

2.3.1.8 filter(func) 案例

2.3.1.9 sample(withReplacement, fraction, seed) 案例

2.3.1.10 distinct([numTasks])) 案例

2.3.1.11 coalesce(numPartitions) 案例

2.3.1.12 repartition(numPartitions) 案例

2.3.1.13 coalesce和repartition的区别

2.3.1.14 sortBy(func,[ascending], [numTasks]) 案例

2.3.1.15 pipe(command, [envVars]) 案例

2.3.2 双Value类型交互

2.3.2.1 union(otherDataset) 案例

2.3.2.2 subtract (otherDataset) 案例

2.3.2.3 intersection(otherDataset) 案例

2.3.2.4 cartesian(otherDataset) 案例

2.3.2.5 zip(otherDataset)案例

2.3.3 Key-Value类型

2.3.3.1 partitionBy案例

2.3.3.2 groupByKey案例

2.3.3.3 reduceByKey(func, [numTasks]) 案例

2.3.3.4 reduceByKey和groupByKey的区别

2.3.3.5 aggregateByKey案例

2.3.3.6 foldByKey案例

2.3.3.7 combineByKey[C] 案例

2.3.3.8 sortByKey([ascending], [numTasks]) 案例

2.3.3.9 mapValues案例

2.3.3.10 join(otherDataset, [numTasks]) 案例

2.3.3.11 cogroup(otherDataset, [numTasks]) 案例

2.3.4 案例实操

 

 

 

RDD的操作算子包括两类,一类叫做transformations,它是用来将RDD进行转化,构建RDD的血缘关系;另一类叫做actions,它是用来触发RDD的计算,得到RDD的相关计算结果或者将RDD保存的文件系统中。

第1章 RDD概述

1.1 什么是RDD

RDD(Resilient Distributed Dataset)叫做分布式弹性数据集,是Spark中最基本的数据抽象。代码中是一个抽象类,它代表一个不可变、可分区、里面的元素可并行计算的集合。

1.2 RDD的属性

 

  1. 一组分区(Partition),即数据集的基本组成单位;
  2. 一个计算每个分区的函数;
  3. RDD之间的依赖关系;
  4. 一个Partitioner,即RDD的分片函数;
  5. 一个列表,存储存取每个Partition的优先位置(preferred location)。

1.3 RDD特点

RDD表示只读的分区的数据集,对RDD进行改动,只能通过RDD的转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必需的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。

1.3.1 分区

RDD逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个compute函数得到每个分区的数据。如果RDD是通过已有的文件系统构建,则compute函数是读取指定文件系统中的数据,如果RDD是通过其他RDD转换而来,则compute函数是执行转换逻辑将其他RDD的数据进行转换。

 

1.3.2 只读

如下图所示,RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD。

 

 由一个RDD转换到另一个RDD,可以通过丰富的操作算子实现,不再像MapReduce那样只能写map和reduce了,如下图所示。

 

 RDD的操作算子包括两类,一类叫做transformations,它是用来将RDD进行转化,构建RDD的血缘关系;另一类叫做actions,它是用来触发RDD的计算,得到RDD的相关计算结果或者将RDD保存的文件系统中。下图是RDD所支持的操作算子列表。

1.3.3 依赖

RDDs通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系,也称之为依赖。如下图所示,依赖包括两种,一种是窄依赖,RDDs之间分区是一一对应的,另一种是宽依赖,下游RDD的每个分区与上游RDD(也称之为父RDD)的每个分区都有关,是多对多的关系。

 

1.3.4 缓存

如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。如下图所示,RDD-1经过一系列的转换后得到RDD-n并保存到hdfs,RDD-1在这一过程中会有个中间结果,如果将其缓存到内存,那么在随后的RDD-1转换到RDD-m这一过程中,就不会计算其之前的RDD-0了。

 

1.3.5 CheckPoint

虽然RDD的血缘关系天然地可以实现容错,当RDD的某个分区数据失败或丢失,可以通过血缘关系重建。但是对于长时间迭代型应用来说,随着迭代的进行,RDDs之间的血缘关系会越来越长,一旦在后续迭代过程中出错,则需要通过非常长的血缘关系去重建,势必影响性能。为此,RDD支持checkpoint将数据保存到持久化的存储中,这样就可以切断之前的血缘关系,因为checkpoint后的RDD不需要知道它的父RDDs了,它可以从checkpoint处拿到数据。

第2章 RDD编程

2.1 编程模型

在Spark中,RDD被表示为对象,通过对象上的方法调用来对RDD进行转换。经过一系列的transformations定义RDD之后,就可以调用actions触发RDD的计算,action可以是向应用程序返回结果(count, collect等),或者是向存储系统保存数据(saveAsTextFile等)。在Spark中,只有遇到action,才会执行RDD的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换。

    要使用Spark,开发者需要编写一个Driver程序,它被提交到集群以调度运行Worker,如下图所示。Driver中定义了一个或多个RDD,并调用RDD上的action,Worker则执行RDD分区计算任务。

 

 

2.2 RDD的创建

在Spark中创建RDD的创建方式可以分为三种:从集合中创建RDD;从外部存储创建RDD;从其他RDD创建。

2.2.1 从集合中创建

从集合中创建RDD,Spark主要提供了两种函数:parallelize和makeRDD

1)使用parallelize()从集合创建

scala> val rdd = sc.parallelize(Array(1,2,3,4,5,6,7,8))

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at <console>:24

2)使用makeRDD()从集合创建

scala> val rdd1 = sc.makeRDD(Array(1,2,3,4,5,6,7,8))

rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at makeRDD at <console>:24

 

2.2.2 由外部存储系统的数据集创建

包括本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、Cassandra、HBase等,我们会在第4章详细介绍。

scala> val rdd2= sc.textFile("hdfs://hadoop102:9000/RELEASE")

rdd2: org.apache.spark.rdd.RDD[String] = hdfs:// hadoop102:9000/RELEASE MapPartitionsRDD[4] at textFile at <console>:24

2.2.3 从其他RDD创建

详见2.3

2.3 RDD的转换(面试开发重点)

RDD整体上分为Value类型和Key-Value类型

2.3.1 Value类型

2.3.1.1 map(func)案例

1. 作用:返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成

2. 需求:创建一个1-10数组的RDD,将所有元素*2形成新的RDD

(1)创建

scala> var source  = sc.parallelize(1 to 10)

source: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[8] at parallelize at <console>:24

(2)打印

scala> source.collect()

res7: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

(3)将所有元素*2

scala> val mapadd = source.map(_ * 2)

mapadd: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[9] at map at <console>:26

(4)打印最终结果

scala> mapadd.collect()

res8: Array[Int] = Array(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

API操作:

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/** map算子
  *
  * @author cherry
  * @create 2019-09-15-19:28
  */
object Opera1 {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("Opera1"))
    //map算子
    val listRDD: RDD[Int] = sc.makeRDD(1 to 10)
    //乘二转换
    //    val mapRDD: RDD[Int] = listRDD.map(x => x * 2)
    val mapRDD: RDD[Int] = listRDD.map(_ * 2) //简化代码
    mapRDD.collect() foreach (println)
  }

}

 

2.3.1.2 mapPartitions(func) 案例

1. 作用:类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]。假设有N个元素,有M个分区,那么map的函数的将被调用N次,而mapPartitions被调用M次,一个函数一次处理所有分区。

2. 需求:创建一个RDD,使每个元素*2组成新的RDD

(1)创建一个RDD

scala> val rdd = sc.parallelize(Array(1,2,3,4))

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[4] at parallelize at <console>:24

(2)使每个元素*2组成新的RDD

scala> rdd.mapPartitions(x=>x.map(_*2))

res3: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[6] at mapPartitions at <console>:27

(3)打印新的RDD

scala> res3.collect

res4: Array[Int] = Array(2, 4, 6, 8)

API操作

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaParititions {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[Int] = sc.makeRDD(1 to 10)
    //mapPartitions可以对RDD中所有的分区进行遍历,其效率高于map算子,但是可能会导致内存溢出
    val mapPartitionsRDD: RDD[Int] = listRDD.mapPartitions(datas => {
      datas.map(data => data * 2)
    })
    mapPartitionsRDD.collect().foreach(println)
  }

}

 

2.3.1.3 mapPartitionsWithIndex(func) 案例

1. 作用:类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Interator[T]) => Iterator[U];

2. 需求:创建一个RDD,使每个元素跟所在分区形成一个元组组成一个新的RDD

(1)创建一个RDD

scala> val rdd = sc.parallelize(Array(1,2,3,4))

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[4] at parallelize at <console>:24

(2)使每个元素跟所在分区形成一个元组组成一个新的RDD

scala> val indexRdd = rdd.mapPartitionsWithIndex((index,items)=>(items.map((index,_))))

indexRdd: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[5] at mapPartitionsWithIndex at <console>:26

(3)打印新的RDD

scala> indexRdd.collect

res2: Array[(Int, Int)] = Array((0,1), (0,2), (1,3), (1,4))

API操作

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaParititions2 {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[Int] = sc.makeRDD(1 to 10,2)//放在2个分区里
    //mapPartitions可以对RDD中所有的分区进行遍历,其效率高于map算子,但是可能会导致内存溢出
    val mapPartitionsRDD: RDD[(Int, String)] = listRDD.mapPartitionsWithIndex {
      case (num, datas) => {
        datas.map((_, "分区号:" + num))
      }
    }
    mapPartitionsRDD.collect().foreach(println)
  }
}

 

2.3.1.4 flatMap(func) 案例

1. 作用:类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)

2. 需求:创建一个元素为1-5的RDD,运用flatMap创建一个新的RDD,新的RDD为原RDD的每个元素的2倍(2,4,6,8,10)

(1)创建

scala> val sourceFlat = sc.parallelize(1 to 5)

sourceFlat: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[12] at parallelize at <console>:24

(2)打印

scala> sourceFlat.collect()

res11: Array[Int] = Array(1, 2, 3, 4, 5)

(3)根据原RDD创建新RDD(1->1,2->1,2……5->1,2,3,4,5)

scala> val flatMap = sourceFlat.flatMap(1 to _)

flatMap: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[13] at flatMap at <console>:26

(4)打印新RDD

scala> flatMap.collect()

res12: Array[Int] = Array(1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5)

API操作

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaFlatMap {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[List[Int]] = sc.makeRDD(Array(List(1,2),List(3,4)))
    val flatMap: RDD[Int] = listRDD.flatMap(datas=>datas)
    flatMap.collect().foreach(println)
  }
}

 

2.3.1.5 map()和mapPartition()的区别

1. map():每次处理一条数据。

2. mapPartition():每次处理一个分区的数据,这个分区的数据处理完后,原RDD中分区的数据才能释放,可能导致OOM。

3. 开发指导:当内存空间较大的时候建议使用mapPartition(),以提高处理效率。

2.3.1.6 glom案例

1. 作用:将每一个分区形成一个数组,形成新的RDD类型时RDD[Array[T]]

2. 需求:创建一个4个分区的RDD,并将每个分区的数据放到一个数组

(1)创建

scala> val rdd = sc.parallelize(1 to 16,4)

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[65] at parallelize at <console>:24

(2)将每个分区的数据放到一个数组并收集到Driver端打印

scala> rdd.glom().collect()

res25: Array[Array[Int]] = Array(Array(1, 2, 3, 4), Array(5, 6, 7, 8), Array(9, 10, 11, 12), Array(13, 14, 15, 16))

API操作

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaGlom {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 4)//1,2  3,4,5  6,7  8,9,10
    //将一个分区的数据放到一个数组中
    val listGlom: RDD[Array[Int]] = listRDD.glom()
    listGlom.collect().foreach(array=>{println(array.mkString(","))})
  }
}

 

2.3.1.7 groupBy(func)案例

1. 作用:分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。

2. 需求:创建一个RDD,按照元素模以2的值进行分组。

(1)创建

scala> val rdd = sc.parallelize(1 to 4)

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[65] at parallelize at <console>:24

(2)按照元素模以2的值进行分组

scala> val group = rdd.groupBy(_%2)

group: org.apache.spark.rdd.RDD[(Int, Iterable[Int])] = ShuffledRDD[2] at groupBy at <console>:26

(3)打印结果

scala> group.collect

res0: Array[(Int, Iterable[Int])] = Array((0,CompactBuffer(2, 4)), (1,CompactBuffer(1, 3)))

API操作

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaGroupBy {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 4) //1,2  3,4,5  6,7  8,9,10
    //将一个分区的数据放到一个数组中
    val groupByRDD: RDD[(Int, Iterable[Int])] = listRDD.groupBy((i => i % 2))
    groupByRDD.collect().foreach(println)

  }
}

 

2.3.1.8 filter(func) 案例

1. 作用:过滤。返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成。

2. 需求:创建一个RDD(由字符串组成),过滤出一个新RDD(包含”xiao”子串)

(1)创建

scala> var sourceFilter = sc.parallelize(Array("xiaoming","xiaojiang","xiaohe","dazhi"))

sourceFilter: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[10] at parallelize at <console>:24

(2)打印

scala> sourceFilter.collect()

res9: Array[String] = Array(xiaoming, xiaojiang, xiaohe, dazhi)

(3)过滤出含” xiao”子串的形成一个新的RDD

scala> val filter = sourceFilter.filter(_.contains("xiao"))

filter: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[11] at filter at <console>:26

(4)打印新RDD

scala> filter.collect()

res10: Array[String] = Array(xiaoming, xiaojiang, xiaohe)

API操作

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaFilter {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 4)
    //将一个分区的数据放到一个数组中
    val listFilter: RDD[Int] = listRDD.filter(x=>x%2==0)
    listFilter.collect().foreach(println)

  }
}

 

2.3.1.9 sample(withReplacement, fraction, seed) 案例

1. 作用:以指定的随机种子随机抽样出数量为fraction的数据,withReplacement表示是抽出的数据是否放回,true为有放回的抽样,false为无放回的抽样,seed用于指定随机数生成器种子。

2. 需求:创建一个RDD(1-10),从中选择放回和不放回抽样

(1)创建RDD

scala> val rdd = sc.parallelize(1 to 10)

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[20] at parallelize at <console>:24

(2)打印

scala> rdd.collect()

res15: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

(3)放回抽样

scala> var sample1 = rdd.sample(true,0.4,2)

sample1: org.apache.spark.rdd.RDD[Int] = PartitionwiseSampledRDD[21] at sample at <console>:26

(4)打印放回抽样结果

scala> sample1.collect()

res16: Array[Int] = Array(1, 2, 2, 7, 7, 8, 9)

(5)不放回抽样

scala> var sample2 = rdd.sample(false,0.2,3)

sample2: org.apache.spark.rdd.RDD[Int] = PartitionwiseSampledRDD[22] at sample at <console>:26

(6)打印不放回抽样结果

scala> sample2.collect()

res17: Array[Int] = Array(1, 9)

API操作

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaSample {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 4)
    val sampleRDD: RDD[Int] = listRDD.sample(false, 0.3, 1)
    sampleRDD.collect().foreach(println)

    /** 打印结果为(无规律的三位随机集合元素)
      * 3
      * 6
      * 8
      */
  }
}

 

2.3.1.10 distinct([numTasks])) 案例

1. 作用:对源RDD进行去重后返回一个新的RDD。默认情况下,只有8个并行任务来操作,但是可以传入一个可选的numTasks参数改变它。

2. 需求:创建一个RDD,使用distinct()对其去重。

(1)创建一个RDD

scala> val distinctRdd = sc.parallelize(List(1,2,1,5,2,9,6,1))

distinctRdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[34] at parallelize at <console>:24

(2)对RDD进行去重(不指定并行度)

scala> val unionRDD = distinctRdd.distinct()

unionRDD: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[37] at distinct at <console>:26

(3)打印去重后生成的新RDD

scala> unionRDD.collect()

res20: Array[Int] = Array(1, 9, 5, 6, 2)

(4)对RDD(指定并行度为2)

scala> val unionRDD = distinctRdd.distinct(2)

unionRDD: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[40] at distinct at <console>:26

(5)打印去重后生成的新RDD

scala> unionRDD.collect()

res21: Array[Int] = Array(6, 2, 1, 9, 5)

API操作

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaDistinct {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2, 5, 4, 2), 4)
    val listDistinct: RDD[Int] = listRDD.distinct().sortBy(x => x) //需要排序
    listDistinct.collect().foreach(println)
    
  }
}

 

2.3.1.11 coalesce(numPartitions) 案例

1. 作用:缩减分区数,用于大数据集过滤后,提高小数据集的执行效率。

2. 需求:创建一个4个分区的RDD,对其缩减分区

(1)创建一个RDD

scala> val rdd = sc.parallelize(1 to 16,4)

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[54] at parallelize at <console>:24

(2)查看RDD的分区数

scala> rdd.partitions.size

res20: Int = 4

(3)对RDD重新分区

scala> val coalesceRDD = rdd.coalesce(3)

coalesceRDD: org.apache.spark.rdd.RDD[Int] = CoalescedRDD[55] at coalesce at <console>:26

(4)查看新RDD的分区数

scala> coalesceRDD.partitions.size

res21: Int = 3

API操作

package RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaCoalesce {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[Int] = sc.makeRDD(1 to 16, 4)
    val coalesced: RDD[Int] = listRDD.coalesce(3)
    println("缩减分区前=" + listRDD.partitions.size)
    listRDD.foreach(println)
    println("缩减分区后=" + coalesced.partitions.size)
    coalesced.foreach(println)
    coalesced.saveAsTextFile("Spark\\sparkWordCount20190915\\src\\main\\scala\\files\\output")

    /** 打印结果为
      * 缩减分区前=4
      * 缩减分区后=3
      */
  }
}

查看输出文件内容

 

2.3.1.12 repartition(numPartitions) 案例

1. 作用:根据分区数,重新通过网络随机洗牌所有数据。

2. 需求:创建一个4个分区的RDD,对其重新分区

(1)创建一个RDD

scala> val rdd = sc.parallelize(1 to 16,4)

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[56] at parallelize at <console>:24

(2)查看RDD的分区数

scala> rdd.partitions.size

res22: Int = 4

(3)对RDD重新分区

scala> val rerdd = rdd.repartition(2)

rerdd: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[60] at repartition at <console>:26

(4)查看新RDD的分区数

scala> rerdd.partitions.size

res23: Int = 2

API操作

package RDD.value

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author cherry
  * @create 2019-09-15-19:39
  */
object OperaReparititions {
  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("OperaParititions"))
    val listRDD: RDD[Int] = sc.makeRDD(1 to 10,4)
    val listRepartiotion: RDD[Int] = listRDD.repartition(3)
    listRDD.collect().foreach(println)
    listRepartiotion.collect().foreach(println)
  }
}

 

2.3.1.13 coalesce和repartition的区别

1. coalesce重新分区,可以选择是否进行shuffle过程。由参数shuffle: Boolean = false/true决定。

2. repartition实际上是调用的coalesce,默认是进行shuffle的。源码如下:

def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
  coalesce(numPartitions, shuffle = true)
}

2.3.1.14 sortBy(func,[ascending], [numTasks]) 案例

1. 作用;使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序。

2. 需求:创建一个RDD,按照不同的规则进行排序

(1)创建一个RDD

scala> val rdd = sc.parallelize(List(2,1,3,4))

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[21] at parallelize at <console>:24

(2)按照自身大小排序

scala> rdd.sortBy(x => x).collect()

res11: Array[Int] = Array(1, 2, 3, 4)

(3)按照与3余数的大小排序

scala> rdd.sortBy(x => x%3).collect()

res12: Array[Int] = Array(3, 4, 1, 2)

2.3.1.15 pipe(command, [envVars]) 案例

1. 作用:管道,针对每个分区,都执行一个shell脚本,返回输出的RDD。

注意:脚本需要放在Worker节点可以访问到的位置

2. 需求:编写一个脚本,使用管道将脚本作用于RDD上。

(1)编写一个脚本

Shell脚本

#!/bin/sh

echo "AA"

while read LINE; do

   echo ">>>"${LINE}

done

(2)创建一个只有一个分区的RDD

scala> val rdd = sc.parallelize(List("hi","Hello","how","are","you"),1)

rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[50] at parallelize at <console>:24

(3)将脚本作用该RDD并打印

scala> rdd.pipe("/opt/module/spark/pipe.sh").collect()

res18: Array[String] = Array(AA, >>>hi, >>>Hello, >>>how, >>>are, >>>you)

(4)创建一个有两个分区的RDD

scala> val rdd = sc.parallelize(List("hi","Hello","how","are","you"),2)

rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[52] at parallelize at <console>:24

(5)将脚本作用该RDD并打印

scala> rdd.pipe("/opt/module/spark/pipe.sh").collect()

res19: Array[String] = Array(AA, >>>hi, >>>Hello, AA, >>>how, >>>are, >>>you)

2.3.2 双Value类型交互

2.3.2.1 union(otherDataset) 案例

1. 作用:对源RDD和参数RDD求并集后返回一个新的RDD

2. 需求:创建两个RDD,求并集

(1)创建第一个RDD

scala> val rdd1 = sc.parallelize(1 to 5)

rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[23] at parallelize at <console>:24

(2)创建第二个RDD

scala> val rdd2 = sc.parallelize(5 to 10)

rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[24] at parallelize at <console>:24

(3)计算两个RDD的并集

scala> val rdd3 = rdd1.union(rdd2)

rdd3: org.apache.spark.rdd.RDD[Int] = UnionRDD[25] at union at <console>:28

(4)打印并集结果

scala> rdd3.collect()

res18: Array[Int] = Array(1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10)

2.3.2.2 subtract (otherDataset) 案例

1. 作用:计算差的一种函数,去除两个RDD中相同的元素,不同的RDD将保留下来

2. 需求:创建两个RDD,求第一个RDD与第二个RDD的差集

(1)创建第一个RDD

scala> val rdd = sc.parallelize(3 to 8)

rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[70] at parallelize at <console>:24

(2)创建第二个RDD

scala> val rdd1 = sc.parallelize(1 to 5)

rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[71] at parallelize at <console>:24

(3)计算第一个RDD与第二个RDD的差集并打印

scala> rdd.subtract(rdd1).collect()

res27: Array[Int] = Array(8, 6, 7)

2.3.2.3 intersection(otherDataset) 案例

1. 作用:对源RDD和参数RDD求交集后返回一个新的RDD

2. 需求:创建两个RDD,求两个RDD的交集

(1)创建第一个RDD

scala> val rdd1 = sc.parallelize(1 to 7)

rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[26] at parallelize at <console>:24

(2)创建第二个RDD

scala> val rdd2 = sc.parallelize(5 to 10)

rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[27] at parallelize at <console>:24

(3)计算两个RDD的交集

scala> val rdd3 = rdd1.intersection(rdd2)

rdd3: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[33] at intersection at <console>:28

(4)打印计算结果

scala> rdd3.collect()

res19: Array[Int] = Array(5, 6, 7)

2.3.2.4 cartesian(otherDataset) 案例

1. 作用:笛卡尔积(尽量避免使用)

2. 需求:创建两个RDD,计算两个RDD的笛卡尔积

(1)创建第一个RDD

scala> val rdd1 = sc.parallelize(1 to 3)

rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[47] at parallelize at <console>:24

(2)创建第二个RDD

scala> val rdd2 = sc.parallelize(2 to 5)

rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[48] at parallelize at <console>:24

(3)计算两个RDD的笛卡尔积并打印

scala> rdd1.cartesian(rdd2).collect()

res17: Array[(Int, Int)] = Array((1,2), (1,3), (1,4), (1,5), (2,2), (2,3), (2,4), (2,5), (3,2), (3,3), (3,4), (3,5))

2.3.2.5 zip(otherDataset)案例

1. 作用:将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。

2. 需求:创建两个RDD,并将两个RDD组合到一起形成一个(k,v)RDD

(1)创建第一个RDD

scala> val rdd1 = sc.parallelize(Array(1,2,3),3)

rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at parallelize at <console>:24

(2)创建第二个RDD(与1分区数相同)

scala> val rdd2 = sc.parallelize(Array("a","b","c"),3)

rdd2: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[2] at parallelize at <console>:24

(3)第一个RDD组合第二个RDD并打印

scala> rdd1.zip(rdd2).collect

res1: Array[(Int, String)] = Array((1,a), (2,b), (3,c))

(4)第二个RDD组合第一个RDD并打印

scala> rdd2.zip(rdd1).collect

res2: Array[(String, Int)] = Array((a,1), (b,2), (c,3))

(5)创建第三个RDD(与1,2分区数不同)

scala> val rdd3 = sc.parallelize(Array("a","b","c"),2)

rdd3: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[5] at parallelize at <console>:24

(6)第一个RDD组合第三个RDD并打印

scala> rdd1.zip(rdd3).collect

java.lang.IllegalArgumentException: Can't zip RDDs with unequal numbers of partitions: List(3, 2)

  at org.apache.spark.rdd.ZippedPartitionsBaseRDD.getPartitions(ZippedPartitionsRDD.scala:57)

  at org.apache.spark.rdd.RDD$$anonfun$partitions$2.apply(RDD.scala:252)

  at org.apache.spark.rdd.RDD$$anonfun$partitions$2.apply(RDD.scala:250)

  at scala.Option.getOrElse(Option.scala:121)

  at org.apache.spark.rdd.RDD.partitions(RDD.scala:250)

  at org.apache.spark.SparkContext.runJob(SparkContext.scala:1965)

  at org.apache.spark.rdd.RDD$$anonfun$collect$1.apply(RDD.scala:936)

  at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)

  at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)

  at org.apache.spark.rdd.RDD.withScope(RDD.scala:362)

  at org.apache.spark.rdd.RDD.collect(RDD.scala:935)

  ... 48 elided

2.3.3 Key-Value类型

2.3.3.1 partitionBy案例

1. 作用:对pairRDD进行分区操作,如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区, 否则会生成ShuffleRDD,即会产生shuffle过程。

2. 需求:创建一个4个分区的RDD,对其重新分区

(1)创建一个RDD

scala> val rdd = sc.parallelize(Array((1,"aaa"),(2,"bbb"),(3,"ccc"),(4,"ddd")),4)

rdd: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[44] at parallelize at <console>:24

(2)查看RDD的分区数

scala> rdd.partitions.size

res24: Int = 4

(3)对RDD重新分区

scala> var rdd2 = rdd.partitionBy(new org.apache.spark.HashPartitioner(2))

rdd2: org.apache.spark.rdd.RDD[(Int, String)] = ShuffledRDD[45] at partitionBy at <console>:26

(4)查看新RDD的分区数

scala> rdd2.partitions.size

res25: Int = 2

2.3.3.2 groupByKey案例

1. 作用:groupByKey也是对每个key进行操作,但只生成一个sequence。

2. 需求:创建一个pairRDD,将相同key对应值聚合到一个sequence中,并计算相同key对应值的相加结果。

(1)创建一个pairRDD

scala> val words = Array("one", "two", "two", "three", "three", "three")

words: Array[String] = Array(one, two, two, three, three, three)

 

scala> val wordPairsRDD = sc.parallelize(words).map(word => (word, 1))

wordPairsRDD: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[4] at map at <console>:26

(2)将相同key对应值聚合到一个sequence中

scala> val group = wordPairsRDD.groupByKey()

group: org.apache.spark.rdd.RDD[(String, Iterable[Int])] = ShuffledRDD[5] at groupByKey at <console>:28

(3)打印结果

scala> group.collect()

res1: Array[(String, Iterable[Int])] = Array((two,CompactBuffer(1, 1)), (one,CompactBuffer(1)), (three,CompactBuffer(1, 1, 1)))

(4)计算相同key对应值的相加结果

scala> group.map(t => (t._1, t._2.sum))

res2: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[6] at map at <console>:31

(5)打印结果

scala> res2.collect()

res3: Array[(String, Int)] = Array((two,2), (one,1), (three,3))

2.3.3.3 reduceByKey(func, [numTasks]) 案例

1. 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置。

2. 需求:创建一个pairRDD,计算相同key对应值的相加结果

(1)创建一个pairRDD

scala> val rdd = sc.parallelize(List(("female",1),("male",5),("female",5),("male",2)))

rdd: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[46] at parallelize at <console>:24

(2)计算相同key对应值的相加结果

scala> val reduce = rdd.reduceByKey((x,y) => x+y)

reduce: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[47] at reduceByKey at <console>:26

(3)打印结果

scala> reduce.collect()

res29: Array[(String, Int)] = Array((female,6), (male,7))

2.3.3.4 reduceByKey和groupByKey的区别

1. reduceByKey:按照key进行聚合,在shuffle之前有combine(预聚合)操作,返回结果是RDD[k,v].

2. groupByKey:按照key进行分组,直接进行shuffle。

3. 开发指导:reduceByKey比groupByKey,建议使用。但是需要注意是否会影响业务逻辑。

2.3.3.5 aggregateByKey案例

参数:(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)

1. 作用:在kv对的RDD中,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),将key与计算结果作为一个新的kv对输出。

2. 参数描述:

(1)zeroValue:给每一个分区中的每一个key一个初始值;

(2)seqOp:函数用于在每一个分区中用初始值逐步迭代value;

(3)combOp:函数用于合并每个分区中的结果。

3. 需求:创建一个pairRDD,取出每个分区相同key对应值的最大值,然后相加

4. 需求分析

 

 

图1-aggregate案例分析

(1)创建一个pairRDD

scala> val rdd = sc.parallelize(List(("a",3),("a",2),("c",4),("b",3),("c",6),("c",8)),2)

rdd: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[0] at parallelize at <console>:24

(2)取出每个分区相同key对应值的最大值,然后相加

scala> val agg = rdd.aggregateByKey(0)(math.max(_,_),_+_)

agg: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[1] at aggregateByKey at <console>:26

(3)打印结果

scala> agg.collect()

res0: Array[(String, Int)] = Array((b,3), (a,3), (c,12))

2.3.3.6 foldByKey案例

参数:(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]

  1. 作用:aggregateByKey的简化操作,seqop和combop相同
  2. 需求:创建一个pairRDD,计算相同key对应值的相加结果

(1)创建一个pairRDD

scala> val rdd = sc.parallelize(List((1,3),(1,2),(1,4),(2,3),(3,6),(3,8)),3)

rdd: org.apache.spark.rdd.RDD[(Int, Int)] = ParallelCollectionRDD[91] at parallelize at <console>:24

(2)计算相同key对应值的相加结果

scala> val agg = rdd.foldByKey(0)(_+_)

agg: org.apache.spark.rdd.RDD[(Int, Int)] = ShuffledRDD[92] at foldByKey at <console>:26

(3)打印结果

scala> agg.collect()

res61: Array[(Int, Int)] = Array((3,14), (1,9), (2,3))

2.3.3.7 combineByKey[C] 案例

参数:(createCombiner: V => C,  mergeValue: (C, V) => C,  mergeCombiners: (C, C) => C)

  1. 作用:对相同K,把V合并成一个集合。
  2. 参数描述:

(1)createCombiner: combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值

(2)mergeValue: 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并

(3)mergeCombiners: 由于每个分区都是独立处理的, 因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 就需要使用用户提供的 mergeCombiners() 方法将各个分区的结果进行合并。

  1. 需求:创建一个pairRDD,根据key计算每种key的均值。(先计算每个key出现的次数以及可以对应值的总和,再相除得到结果)
  2. 需求分析:

 

 

图2- combineByKey案例分析

(1)创建一个pairRDD

scala> val input = sc.parallelize(Array(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)),2)

input: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[52] at parallelize at <console>:26

(2)将相同key对应的值相加,同时记录该key出现的次数,放入一个二元组

scala> val combine = 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))

combine: org.apache.spark.rdd.RDD[(String, (Int, Int))] = ShuffledRDD[5] at combineByKey at <console>:28

(3)打印合并后的结果

scala> combine.collect

res5: Array[(String, (Int, Int))] = Array((b,(286,3)), (a,(274,3)))

(4)计算平均值

scala> val result = combine.map{case (key,value) => (key,value._1/value._2.toDouble)}

result: org.apache.spark.rdd.RDD[(String, Double)] = MapPartitionsRDD[54] at map at <console>:30

(5)打印结果

scala> result.collect()

res33: Array[(String, Double)] = Array((b,95.33333333333333), (a,91.33333333333333))

2.3.3.8 sortByKey([ascending], [numTasks]) 案例

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

2. 需求:创建一个pairRDD,按照key的正序和倒序进行排序

(1)创建一个pairRDD

scala> val rdd = sc.parallelize(Array((3,"aa"),(6,"cc"),(2,"bb"),(1,"dd")))

rdd: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[14] at parallelize at <console>:24

(2)按照key的正序

scala> rdd.sortByKey(true).collect()

res9: Array[(Int, String)] = Array((1,dd), (2,bb), (3,aa), (6,cc))

(3)按照key的倒序

scala> rdd.sortByKey(false).collect()

res10: Array[(Int, String)] = Array((6,cc), (3,aa), (2,bb), (1,dd))

2.3.3.9 mapValues案例

1. 针对于(K,V)形式的类型只对V进行操作

2. 需求:创建一个pairRDD,并将value添加字符串"|||"

(1)创建一个pairRDD

scala> val rdd3 = sc.parallelize(Array((1,"a"),(1,"d"),(2,"b"),(3,"c")))

rdd3: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[67] at parallelize at <console>:24

(2)对value添加字符串"|||"

scala> rdd3.mapValues(_+"|||").collect()

res26: Array[(Int, String)] = Array((1,a|||), (1,d|||), (2,b|||), (3,c|||))

2.3.3.10 join(otherDataset, [numTasks]) 案例

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

2. 需求:创建两个pairRDD,并将key相同的数据聚合到一个元组

(1)创建第一个pairRDD

scala> val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))

rdd: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[32] at parallelize at <console>:24

(2)创建第二个pairRDD

scala> val rdd1 = sc.parallelize(Array((1,4),(2,5),(3,6)))

rdd1: org.apache.spark.rdd.RDD[(Int, Int)] = ParallelCollectionRDD[33] at parallelize at <console>:24

(3)join操作并打印结果

scala> rdd.join(rdd1).collect()

res13: Array[(Int, (String, Int))] = Array((1,(a,4)), (2,(b,5)), (3,(c,6)))

2.3.3.11 cogroup(otherDataset, [numTasks]) 案例

1. 作用:在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD

2. 需求:创建两个pairRDD,并将key相同的数据聚合到一个迭代器

(1)创建第一个pairRDD

scala> val rdd = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))

rdd: org.apache.spark.rdd.RDD[(Int, String)] = ParallelCollectionRDD[37] at parallelize at <console>:24

(2)创建第二个pairRDD

scala> val rdd1 = sc.parallelize(Array((1,4),(2,5),(3,6)))

rdd1: org.apache.spark.rdd.RDD[(Int, Int)] = ParallelCollectionRDD[38] at parallelize at <console>:24

(3)cogroup两个RDD并打印结果

scala> rdd.cogroup(rdd1).collect()

res14: Array[(Int, (Iterable[String], Iterable[Int]))] = Array((1,(CompactBuffer(a),CompactBuffer(4))), (2,(CompactBuffer(b),CompactBuffer(5))), (3,(CompactBuffer(c),CompactBuffer(6))))

2.3.4 案例实操

1. 数据结构:时间戳,省份,城市,用户,广告,中间字段使用空格分割。

 

样本如下: 

1516609143867 6 7 64 16

1516609143869 9 4 75 18

1516609143869 1 7 87 12

2. 需求:统计出每一个省份广告被点击次数的TOP3

3. 实现过程:

package com.package.practice

 

import org.apache.spark.rdd.RDD

import org.apache.spark.{SparkConf, SparkContext}

 

//需求:统计出每一个省份广告被点击次数的TOP3

object Practice {

 

  def main(args: Array[String]): Unit = {

 

    //1.初始化spark配置信息并建立与spark的连接

    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Practice")

    val sc = new SparkContext(sparkConf)

 

    //2.读取数据生成RDD:TS,Province,City,User,AD

    val line = sc.textFile("E:\\IDEAWorkSpace\\SparkTest\\src\\main\\resources\\agent.log")

 

    //3.按照最小粒度聚合:((Province,AD),1)

    val provinceAdToOne = line.map { x =>

      val fields: Array[String] = x.split(" ")

      ((fields(1), fields(4)), 1)

    }

 

    //4.计算每个省中每个广告被点击的总数:((Province,AD),sum)

    val provinceAdToSum = provinceAdToOne.reduceByKey(_ + _)

 

    //5.将省份作为key,广告加点击数为value:(Province,(AD,sum))

    val provinceToAdSum = provinceAdToSum.map(x => (x._1._1, (x._1._2, x._2)))

 

    //6.将同一个省份的所有广告进行聚合(Province,List((AD1,sum1),(AD2,sum2)...))

    val provinceGroup = provinceToAdSum.groupByKey()

 

    //7.对同一个省份所有广告的集合进行排序并取前3条,排序规则为广告点击总数

    val provinceAdTop3 = provinceGroup.mapValues { x =>

      x.toList.sortWith((x, y) => x._2 > y._2).take(3)

    }

    //8.将数据拉取到Driver端并打印

    provinceAdTop3.collect().foreach(println)

    //9.关闭与spark的连接

    sc.stop()

  }  

}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值