Spark核心编程3-map,mapPartitions,flatMap,glom,分区,groupby

本文详细介绍了Spark中RDD的转换操作,包括map、mapPartitions和glom等算子,分析了它们在数据处理中的作用和性能差异。map算子逐条处理数据,mapPartitions则是以分区为单位批量处理,性能更高但可能占用更多内存。此外,文章还讨论了分区不变的概念和groupby操作,以及shuffle过程中数据的重新组合。
摘要由CSDN通过智能技术生成

一、算子介绍(RDD方法既称为算子)

RDD方法分为转换和行动两个内容。

转换:功能的补充,也就是复杂业务逻辑,生产多个RDD的过程,旧的RDD包装成新的RDD,相互包装补充;

行动:触发任务调度和作业的执行。封装并不会触发任务的执行,如flatmap,map等,只有collect等才能触发。

二、转换算子(RDD方法)

根据数据类型不同可以把算法分为三大类型单value ,双value,key-value;

2.1 map 

map函数式将处理的数据逐条进行映射转换,可以是值或者类型的转换。

scala集合中使用最多的转换映射方法

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo map算子,映射转换
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    //匿名函数格式:() => {}  如(num: Int) => { num * 2 }
//    val makeRDD = rdd.map((num: Int) => {num * 2})
    //根据简化原则 将代码优化
    //一行计算可省略{}
//    val makeRDD = rdd.map((num: Int) => num * 2)
    //参数num可以自动推断出来类型,可省略
//    val makeRDD = rdd.map((num) => num * 2)
    //参数只有一个,可省略()
//    val makeRDD = rdd.map(num => num * 2)
    //参数只在计算逻辑num * 2出现一次,并且是按顺序出现可省略 参数,用_代替
    val makeRDD = rdd.map(_* 2)
    makeRDD.collect().foreach(println)
    sc.stop()
  }
}
在data文件夹下创建一个文件log.log,内容为
127.0.0.0 - - 2021/06/23:23:00:00 /log/information
127.0.0.0 - - 2021/06/23:09:00:00 /log/car

代码如下:

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTrans_test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo map算子,映射转换
    val rdd = sc.textFile("data/log.log")
    val mapRDD = rdd.map(line => {
      val datas = line.split(" ")
      datas(4)
    })
    mapRDD.collect().foreach(println)
    sc.stop()
  }
}

运行结果:
/log/information
/log/car

2.2 map的并行计算

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTrans_Par {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo map算子,映射转换
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    val mapRDD = rdd.map(num => {
      println("====>" + num)
      num
    })
    val mapRDD1 = mapRDD.map(num => {
      println("++++++++>" + num)
      num
    })
    mapRDD1.collect()
    sc.stop()
  }
}

运行结果,无序:
====>4
====>1
====>2
====>3
++++++++>2
++++++++>1
++++++++>4
++++++++>3

设置分区数量为1时
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),1)
运行结果:
====>1
++++++++>1
====>2
++++++++>2
====>3
++++++++>3
====>4
++++++++>4
结论分析:
1.RDD的计算,在一个分区内是逐个进行计算;
2.1个分区内的数据执行是有顺序的;

设置分区数量为2时
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
运行结果:
====>1
====>3
++++++++>1
++++++++>3
====>2
====>4
++++++++>2
++++++++>4
结论分析:
1.不同分区的数据计算时无序的

2.3 mapPartitions(spark类似缓冲区的算子)

def mapPartitions[U: ClassTag](
      f: Iterator[T] => Iterator[U],
      preservesPartitioning: Boolean = false): RDD[U] = withScope {
    val cleanedF = sc.clean(f)
    new MapPartitionsRDD(
      this,
      (context: TaskContext, index: Int, iter: Iterator[T]) => cleanedF(iter),
      preservesPartitioning)
  }

解析:
Iterator[T] => Iterator[U] 表示返回的也是个迭代器
如何转为迭代器?
List(2).iterator

以上效果demo,得知map算子是一个个逻辑RDD执行,并不是批量执行,这效率性能不会很高;

mapPartitions是将一个分区的数据全部拿到再操作;

mapPartitions但是有个问题,当数据处理完毕后是不会被释放的,因为iter作为一个集合,拿去引用目标数据,被拿去处理,当数据没有被处理完毕时,引用是一直存在的,引用存在即意味着数据没有被释放,内存还是在占用着,所以选择哪种方法需要看资源大小。内存较小,数据较大,使用mapPartitions可能会内存溢出,可以使用map(没有对象引用,来一条处理一条);

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo mapPartitions算子,映射转换
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val mapParRDD = rdd.mapPartitions(iter => {
      println("@@@@@@@@@@")
      iter.map(_ * 2)
    })
    mapParRDD.collect().foreach(println)
    sc.stop()
  }
}

结果:
@@@@@@@@@@
@@@@@@@@@@
2
4
6
8
分析:
1.说明两个分区,走两次;
2. iter.map(_ * 2)是在内存中进行操作,性能比较高

map类似串行操作,一个个执行,mapPartitions则以分区为单位进行批量处理;

map算子操作后,数据总量不会改变,mapPartitions需要传递迭代器,返回一个迭代器,数据总量可变化;

map性能较低,mapPartitions性能较高,但是会占用内存资源,资源有限不推荐使用;

2.4 分区索引方法

def mapPartitionsWithIndex[U: ClassTag](
      f: (Int, Iterator[T]) => Iterator[U],
      preservesPartitioning: Boolean = false): RDD[U] = withScope {
    val cleanedF = sc.clean(f)
    new MapPartitionsRDD(
      this,
      (context: TaskContext, index: Int, iter: Iterator[T]) => cleanedF(index, iter),
      preservesPartitioning)
  }

以分区为单位将数据给计算节点进行逻辑计算,可以获取当前分区索引。

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform03 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo mapPartitions算子,映射转换
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val mapPWRDD = rdd.mapPartitionsWithIndex((index, iter) => {
      if (index == 1) {
        iter
      } else {
        Nil.iterator
      }
    })
    mapPWRDD.collect().foreach(println)
    sc.stop()
  }
}

运行结果:
3
4

采用默认分区的时候怎么知道是在哪个分区?

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform02 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo mapPartitions算子,映射转换
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    val mapPWRDD = rdd.mapPartitionsWithIndex((index, iter) => {
      //iter.map()会把每个num取到,再进行操作
      iter.map(num => {
        (index, num)
      })
    })
    mapPWRDD.collect().foreach(println)
    sc.stop()
  }
}


运行结果:
(4,1)
(9,2)
(14,3)
(19,4)

2.5 flatMap

扁平化映射算子,将整体拆分成个体;

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform_flatmap {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo mapPartitions算子,映射转换
    val rdd :RDD[List[Int]]= sc.makeRDD(List(List(1, 2), List(3, 4)))
    //第一个list 是元素List(1, 2)和List(3, 4)
    //第二个list 是flatMap后重新将1,2,3,4封装成为一个新的list
    val flatMapRDD: RDD[Int] = rdd.flatMap(list => {
      //first list:List(1, 2)
      //first list:List(3, 4)
      println("first list:"+list)
      list
    })
    flatMapRDD.collect().foreach(println)
    sc.stop()
  }
}

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform_flatmap01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo mapPartitions算子,映射转换
    val rdd :RDD[String]= sc.makeRDD(List("hello world","hello spark"))
    val flatMapRDD: RDD[String] = rdd.flatMap(s => {
      s.split(" ")
    })
    flatMapRDD.collect().foreach(println)
    sc.stop()
  }
}

运行结果:
hello
world
hello
spark

分析以上两个demo,匿名函数里面的参数会自己推断类型,但是书写参数名建议能看明白,flatMap返回的是一个可迭代的集合;

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform_flatmap02 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(List(List(1, 2), 5, List(3, 4)))

    val flatMapRDD = rdd.flatMap(
      data => {
        data match {
          case list: List[_] => list
          case dat => List(dat)
        }
      })
    flatMapRDD.collect().foreach(println)
    sc.stop()
  }
}

运行结果:
1
2
5
3
4

2.6 glom

将分区内的数据转换为数组进行处理,分区不变

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform_glom {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo glom算子 
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5), 2)
    val glomRDD: RDD[Array[Int]] = rdd.glom()
    glomRDD.collect().foreach(data =>{
      println(data.mkString(","))
    })
    sc.stop()
  }
}

运行结果:
1,2
3,4,5
package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform_glom01 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo glom算子 将数据转为数组进行操作
    //需求:将两个分区内的最大值相加求和
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val rddglom: RDD[Array[Int]] = rdd.glom()
    val rddMax: RDD[Int] = rddglom.map(array => {
      array.max
    })
    println(rddMax.collect().sum)
    sc.stop()
  }
}
结果为:6

2.7 分区不变(数量)的概念

map,flatmap,glom等算子执行后,数据转换后分区是不变的。

2.8 groupby

package com.byxrs.spark_core.operator.transform

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

object SparkRDDTransform_groupby {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo groupBy算子
    //根据数据源的每一个数据进行分组判断,根据返回的分组key进行分组,相同的key会放在一组
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    val groupByRDD: RDD[(Int, Iterable[Int])] = rdd.groupBy(_ % 2)
    groupByRDD.collect().foreach(println)
    sc.stop()
  }
}

运行结果:
(0,CompactBuffer(2, 4))
(1,CompactBuffer(1, 3))

注意区分概念:分组和分区是没有必然的关系的。因为一个组的数据可以放在一个分区中,但是一个分区不一定只有一个组。

shuffle概念引入:groupby会根据指定规则将数据打乱打散,重新组合,但是分区数量默认不变,这个操作称为shuffle,极端情况是所有数据分在同一个区内。

小案例

package com.byxrs.spark_core.operator.transform

import java.text.SimpleDateFormat
import java.util.Date
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
 * 需求:统计不同时间段的访问点击数量
 */
object SparkRDDTransform_groupby_test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
    val sc = new SparkContext(conf)
    //todo groupBy算子
    val rdd: RDD[String] = sc.textFile("data/log.log")
    //Iterable迭代器
    val rddGroupBy: RDD[(String, Iterable[(String, Int)])] = rdd.map(
      line => {
        val datas: Array[String] = line.split(" ")
        val time: String = datas(3)
        val sdf = new SimpleDateFormat("yyyy/MM/dd:HH:mm:ss")
        //public Date parse(String source) throws ParseException
        val date: Date = sdf.parse(time)

        val sdf1 = new SimpleDateFormat("HH")
        //public final String format(Date date)
        val hour: String = sdf1.format(date)
        (hour, 1)
      }).groupBy(_._1) //groupBy(_._1)根据(hour, 1)第一个元素分组统计,既 hour
    //模式匹配
    rddGroupBy.map {
      case (hour, iter) => {
        (hour, iter.size)
      }
    }.collect().foreach(println)

    sc.stop()
  }
}

运行结果:
(09,1)
(23,1)

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值