「这是我参与11月更文挑战的第38天,活动详情查看:2021最后一次更文挑战」。
一、RDD 转换算子
RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value类型
1、Value 类型
1.1、map
-
函数签名
def map[U: ClassTag](f: T => U): RDD[U] 复制代码
-
函数说明
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - map val rdd = sc.makeRDD(List(1, 2, 3, 4)) // 1,2,3,4 // 2,4,6,8 // 转换函数 def mapFunction(num: Int): Int = { num * 2 } //val mapRDD: RDD[Int] = rdd.map(mapFunction) //val mapRDD: RDD[Int] = rdd.map((num:Int)=>{num*2}) //val mapRDD: RDD[Int] = rdd.map((num:Int)=>num*2) //val mapRDD: RDD[Int] = rdd.map((num)=>num*2) //val mapRDD: RDD[Int] = rdd.map(num=>num*2) val mapRDD: RDD[Int] = rdd.map(_ * 2) mapRDD.collect().foreach(println) sc.stop() } 复制代码
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - map 分区 // 1. rdd的计算一个分区内的数据是一个一个执行逻辑 // 只有前面一个数据全部的逻辑执行完毕后,才会执行下一个数据。 // 分区内数据的执行是有序的。 // 2. 不同分区数据计算是无序的。 val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) val mapRDD = rdd.map( num => { println(">>>>>>>> " + num) num } ) val mapRDD1 = mapRDD.map( num => { println("######" + num) num } ) mapRDD1.collect() sc.stop() } 复制代码
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - map val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // 【1,2】,【3,4】 rdd.saveAsTextFile("output") val mapRDD = rdd.map(_ * 2) // 【2,4】,【6,8】 mapRDD.saveAsTextFile("output1") sc.stop() } 复制代码
-
小功能:从服务器日志数据 apache.log 中获取用户请求 URL 资源路径
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - map val rdd = sc.textFile("datas/apache.log") // 长的字符串 // 短的字符串 val mapRDD: RDD[String] = rdd.map( line => { val datas = line.split(" ") datas(6) } ) mapRDD.collect().foreach(println) sc.stop() } 复制代码
1.2、mapPartitions
-
函数签名
def mapPartitions[U: ClassTag]( f: Iterator[T] => Iterator[U], preservesPartitioning: Boolean = false): RDD[U] 复制代码
-
函数说明
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据。
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - mapPartitions val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // mapPartitions : 可以以分区为单位进行数据转换操作 // 但是会将整个分区的数据加载到内存进行引用 // 如果处理完的数据是不会被释放掉,存在对象的引用。 // 在内存较小,数据量较大的场合下,容易出现内存溢出。 val mpRDD: RDD[Int] = rdd.mapPartitions( iter => { println(">>>>>>>>>>") iter.map(_ * 2) } ) mpRDD.collect().foreach(println) sc.stop() } 复制代码
-
小功能:获取每个数据分区的最大值
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - mapPartitions val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // 【1,2】,【3,4】 // 【2】,【4】 val mpRDD = rdd.mapPartitions( iter => { List(iter.max).iterator } ) mpRDD.collect().foreach(println) sc.stop() } 复制代码
思考一个问题:map 和 mapPartitions 的区别?
-
数据处理角度
Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子是以分区为单位进行批处理操作。
-
功能的角度
Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,所以可以增加或减少数据
-
性能的角度
Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处理,所以性能较高。但是 mapPartitions 算子会长时间占用内存,那么这样会导致内存可能不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用。使用 map 操作。
1.3、mapPartitionsWithIndex
-
函数签名
def mapPartitionsWithIndex[U: ClassTag]( f: (Int, Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false): RDD[U] 复制代码
-
函数说明
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - mapPartitionsWithIndex val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) val mpiRDD = rdd.mapPartitionsWithIndex( (index, iter) => { // 1, 2, 3, 4 iter.map( num => { (index, num) } ) } ) mpiRDD.collect().foreach(println) sc.stop() } 复制代码
-
小功能:获取第二个数据分区的数据
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - mapPartitionsWithIndex val rdd = sc.makeRDD(List(1, 2, 3, 4), 2) // 【1,2】,【3,4】 val mpiRDD = rdd.mapPartitionsWithIndex( (index, iter) => { if (index == 1) { iter } else { Nil.iterator } } ) mpiRDD.collect().foreach(println) sc.stop() } 复制代码
1.4、flatMap
-
函数签名
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U] 复制代码
-
函数说明
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - flatMap val rdd: RDD[String] = sc.makeRDD(List( "Hello Scala", "Hello Spark" )) val flatRDD: RDD[String] = rdd.flatMap( s => { s.split(" ") } ) flatRDD.collect().foreach(println) sc.stop() } 复制代码
-
小功能:将 List(List(1,2),3,List(4,5))进行扁平化操作
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - flatMap val rdd = sc.makeRDD(List(List(1, 2), 3, List(4, 5))) // val flatRDD = rdd.flatMap( // data => { // data match { // case list: List[_] => list // case dat => List(dat) // } // } // ) val flatRDD = rdd.flatMap { case list: List[_] => list case dat => List(dat) } flatRDD.collect().foreach(println) sc.stop() } 复制代码
1.5、glom
-
函数签名
def glom(): RDD[Array[T]] 复制代码
-
函数说明
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - glom val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2) // List => Int // Int => Array val glomRDD: RDD[Array[Int]] = rdd.glom() glomRDD.collect().foreach(data => println(data.mkString(","))) sc.stop() } 复制代码
-
小功能:计算所有分区最大值求和(分区内取最大值,分区间最大值求和)
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - glom val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2) // 【1,2】,【3,4】 // 【2】,【4】 // 【6】 val glomRDD: RDD[Array[Int]] = rdd.glom() val maxRDD: RDD[Int] = glomRDD.map( array => { array.max } ) println(maxRDD.collect().sum) sc.stop() } 复制代码
1.6、groupBy
-
函数签名
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])] 复制代码
-
函数说明
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为 shuffle。极限情况下,数据可能被分在同一个分区中,一个组的数据在一个分区中,但是并不是说一个分区中只有一个组。
```scala
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator")
val sc = new SparkContext(sparkConf)
// 算子 - groupBy
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
// groupBy会将数据源中的每一个数据进行分组判断,根据返回的分组key进行分组
// 相同的key值的数据会放置在一个组中
def groupFunction(num: Int) = {
num % 2
}
val groupRDD: RDD[(Int, Iterable[Int])] = rdd.groupBy(groupFunction)
groupRDD.collect().foreach(println)
sc.stop()
}
```
复制代码
-
小功能:将 List("Hello", "hive", "hbase", "Hadoop")根据单词首写字母进行分组。
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - groupBy val rdd = sc.makeRDD(List("Hello", "Spark", "Scala", "Hadoop"), 2) // 分组和分区没有必然的关系 val groupRDD = rdd.groupBy(_.charAt(0)) groupRDD.collect().foreach(println) sc.stop() } 复制代码
-
小功能:从服务器日志数据 apache.log 中获取每个时间段访问量。
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - groupBy val rdd = sc.textFile("datas/apache.log") val timeRDD: RDD[(String, Iterable[(String, Int)])] = rdd.map( line => { val datas = line.split(" ") val time = datas(3) //time.substring(0, ) val sdf = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss") val date: Date = sdf.parse(time) val sdf1 = new SimpleDateFormat("HH") val hour: String = sdf1.format(date) (hour, 1) } ).groupBy(_._1) timeRDD.map { case (hour, iter) => { (hour, iter.size) } }.collect.foreach(println) sc.stop() } 复制代码
1.7、filter
-
函数签名
def filter(f: T => Boolean): RDD[T] 复制代码
-
函数说明
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - filter val rdd = sc.makeRDD(List(1, 2, 3, 4)) val filterRDD: RDD[Int] = rdd.filter(num => num % 2 != 0) filterRDD.collect().foreach(println) sc.stop() } 复制代码
-
小功能:从服务器日志数据 apache.log 中获取 2015 年 5 月 17 日的请求路径
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - filter val rdd = sc.textFile("datas/apache.log") rdd.filter( line => { val datas = line.split(" ") val time = datas(3) time.startsWith("17/05/2015") } ).collect().foreach(println) sc.stop() } 复制代码
1.8、sample
-
函数签名
def sample( withReplacement: Boolean, fraction: Double, seed: Long = Utils.random.nextLong): RDD[T] 复制代码
-
函数说明
根据指定的规则从数据集中抽取数据
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - sample val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) // sample算子需要传递三个参数 // 1. 第一个参数表示,抽取数据后是否将数据返回 true(放回),false(丢弃) // 2. 第二个参数表示, // 如果抽取不放回的场合:数据源中每条数据被抽取的概率,基准值的概念 // 如果抽取放回的场合:表示数据源中的每条数据被抽取的可能次数 // 3. 第三个参数表示,抽取数据时随机算法的种子 // 如果不传递第三个参数,那么使用的是当前系统时间 // println(rdd.sample( // false, // 0.4 // //1 // ).collect().mkString(",")) println(rdd.sample( true, 2 //1 ).collect().mkString(",")) sc.stop() } 复制代码
-
思考一个问题:有啥用,抽奖吗?
1.9、distinct
-
函数签名
def distinct()(implicit ord: Ordering[T] = null): RDD[T] def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] 复制代码
-
函数说明
将数据集中重复的数据去重
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - distinct val rdd = sc.makeRDD(List(1, 2, 3, 4, 1, 2, 3, 4)) val rdd1: RDD[Int] = rdd.distinct() rdd1.collect().foreach(println) sc.stop() } 复制代码
-
思考一个问题:如果不用该算子,你有什么办法实现数据去重?
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - distinct val rdd = sc.makeRDD(List(1, 2, 3, 4, 1, 2, 3, 4)) rdd.map(x => (x, null)) .reduceByKey((x, _) => x, 2) .map(_._1) .collect() .foreach(println) sc.stop() } 复制代码
1.10、coalesce
- 函数签名
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T]
复制代码
-
函数说明
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - coalesce val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 3) // coalesce方法默认情况下不会将分区的数据打乱重新组合 // 这种情况下的缩减分区可能会导致数据不均衡,出现数据倾斜 // 如果想要让数据均衡,可以进行shuffle处理 //val newRDD: RDD[Int] = rdd.coalesce(2) val newRDD: RDD[Int] = rdd.coalesce(2, true) newRDD.saveAsTextFile("output") sc.stop() } 复制代码
-
思考一个问题:我想要扩大分区,怎么办?
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - coalesce val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 2) // coalesce算子可以扩大分区的,但是如果不进行shuffle操作,是没有意义,不起作用。 // 所以如果想要实现扩大分区的效果,需要使用shuffle操作 // spark提供了一个简化的操作 // 缩减分区:coalesce,如果想要数据均衡,可以采用shuffle // 扩大分区:repartition, 底层代码调用的就是coalesce,而且肯定采用shuffle //val newRDD: RDD[Int] = rdd.coalesce(3, true) val newRDD: RDD[Int] = rdd.repartition(3) newRDD.saveAsTextFile("output") sc.stop() } 复制代码
1.11、repartition
-
函数签名
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] 复制代码
-
函数说明
该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。无论是将分区数多的RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition操作都可以完成,因为无论如何都会经 shuffle 过程。
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - repartition val rdd = sc.makeRDD(List(1, 2, 3, 4, 5, 6), 2) // coalesce算子可以扩大分区的,但是如果不进行shuffle操作,是没有意义,不起作用。 // 所以如果想要实现扩大分区的效果,需要使用shuffle操作 // spark提供了一个简化的操作 // 缩减分区:coalesce,如果想要数据均衡,可以采用shuffle // 扩大分区:repartition, 底层代码调用的就是coalesce,而且肯定采用shuffle //val newRDD: RDD[Int] = rdd.coalesce(3, true) val newRDD: RDD[Int] = rdd.repartition(3) newRDD.saveAsTextFile("output") sc.stop() } 复制代码
-
思考一个问题:coalesce 和 repartition 区别?
1.12、sortBy
-
函数签名
def sortBy[K]( f: (T) => K, ascending: Boolean = true, numPartitions: Int = this.partitions.length) (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] 复制代码
-
函数说明
该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一致。中间存在 shuffle 的过程
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - sortBy val rdd = sc.makeRDD(List(6, 2, 4, 5, 3, 1), 2) val newRDD: RDD[Int] = rdd.sortBy(num => num) newRDD.saveAsTextFile("output") sc.stop() } 复制代码
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Operator") val sc = new SparkContext(sparkConf) // 算子 - sortBy val rdd = sc.makeRDD(List(("1", 1), ("11", 2), ("2", 3)), 2) // sortBy方法可以根据指定的规则对数据源中的数据进行排序,默认为升序,第二个参数可以改变排序的方式 // sortBy默认情况下,不会改变分区。但是中间存在shuffle操作 val newRDD = rdd.sortBy(t => t._1.toInt, false) newRDD.collect().foreach(println) sc.stop() } 复制代码