SparkCore面试题

1.编写WordCount(读取一个本地文件),并打包到集群运行,说明需要添加的主要参数。

代码

	val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName)
    val sc = new SparkContext(conf)
    val resRDD: RDD[(String, Int)] = sc.textFile(args(0)).flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
    resRDD.saveAsTextFile(args(1))

    sc.stop()

运行jar命令:

bin/spark-submit \
--master yarn \
--deploy-mode cluster \
--executor-cores 2 \
--class com.aura.spark.day06.Spark09_Interview1 \
/home/hadoop/jar/WordCount.jar \
/word_in \
/word_out

命令解析

  • master:指定程序运行模式。
  • deploy-mode:指定Driver运行在集群还是本地。
  • executor-cores:每个Executor的核数。
  • class:Spark程序主类全限定名。

2.RDD的五个特性

  • 一组分区;
  • 每个分区数据的计算规则;
  • 和其他RDD之间的血缘依赖关系;
  • 对于KV类型存储的数据,有一个可选的分区器。
  • 存储切片优先位置列表。

3.如何创建一个RDD,有几种方式,举例说明

四种。

  • makeRDD()或者parallelize()方法创建RDD;
  • textFile()读取文件创建RDD;
  • transformation转换算子通过其他RDD生成新的RDD;
  • 通过new方式创建RDD,比如new JdbcRDD()。

4.创建一个RDD,使其一个分区的数据转变为一个String。例如(Array(“a”,“b”,“c”,“d”),2)=>(“ab”,“cd”)

	val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[String] = sc.makeRDD(Array("a", "b", "c", "d"), 2)
    // 将同一分区数据转为数组
    val glomRDD: RDD[Array[String]] = rdd.glom()
    glomRDD.foreach(println)
    val resRDD: RDD[String] = glomRDD.map(
      arr => {
        arr.mkString("")
      }
    )
    resRDD.foreach(println)

    sc.stop()

5.map与mapPartitions的区别

  • map是对RDD中的元素按照指定规则进行一一映射。
  • mapPartitons是以分区为单位对RDD中的元素按照指定规则进行映射。

6.coalesce与repartition两个算子的作用以及区别与联系

作用

        重新分区。

联系

        repartition底层调用的就是coalesce,但是shuffle参数设置的是true,表示执行shuffle的重新分区。

区别

        coalesce默认shuffle参数为false,表示不执行shuffle重新分区,一般用于减少分区。repartition一般用于增加分区。

7.使用zip算子时需要注意的是什么(即哪些情况不能使用)

  • 在scala集合中,zip函数即使两个集合元素数量不相同,也可以进行zip操作,但是多余的元素不会被返回。
  • 但是在Spark RDD算子中,zip算子只能操作两个分区数元素数量都一致的RDD。

8.reduceByKey跟groupByKey之间的区别

  • reduceByKey:按照key进行聚合,在shuffle之前,会对分区内数据进行预聚合操作,返回的结果是k-v类型的RDD(RDD[K,V])。
  • groupByKey:按照key进行分组直接进行shuffle。返回的结果是k-迭代器类型的RDD(RDD[K,Iterable[V]])。
  • 在不影响业务逻辑的情况下,优先使用reduceByKey。求和操作不影响业务逻辑,求平均值的操作影响业务逻辑。

9.reduceByKey跟aggregateByKey、foldByKey之间的区别与联系

联系

  • 都是按照key做聚合操作,都会在shuffle之前对数据做预聚合操作。

区别

  • reduceByKey:没有初始值,分区内和分区间的计算规则一致。
  • aggregateByKey:有初始值,初始值参与分区内和分区间的计算,分区内和分区间规则可以不一致。
  • foldByKey:有初始值,初始值参与分区内的计算,分区内和分区间计算规则相同。

10.combineByKey的参数作用,说明其参数调用时机

  • 参数1:可以对数据的结构进行转换。
  • 参数2:分区内的计算规则。
  • 参数3:分区间的计算规则。

11.使用RDD实现Join的多种方式

  • rdd1.join(rdd2):将相同的key对应的value关联到一起。如果key只是某个RDD存在,那么不返回。
  • rdd1.leftOuterJoin(rdd2):返回rdd1中的全部key-value和关联后的key-value。
  • rdd1.rightOuterJoin(rdd2):返回rdd2中的全部key-value和关联后的key-value。
  • rdd1.cogroup(rdd2):每个rdd中先关联自己的key形成集合,然后再合并。

12.aggregateByKey与aggregate之间的区别与联系

联系

  • 两者都是对分区内和分区间的元素做聚合操作,而且都有初始值。

区别

  • aggregateByKey是转换算子,是对k-v类型的RDD进行操作,初始值参与分区内和分区间的计算,初始值会和RDD中每一个元素进行迭代并运算。
  • aggregate是行动算子,初始值参与分区内和分区间的计算,分区内计算时,初始值和RDD每个分区中的一个元素按照指定规则运算,分区间计算时,初始值只会参与一次运算。

13.创建一个RDD,自定义一种分区规则并实现?spark中是否可以按照Value分区

自定义分区器

object Spark12_Interview13 {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[(String, String)] = sc.makeRDD(List(("13698624174", "河北"), ("13766887551", "广东"),
      ("13876543211", "上海"), ("17677885551", "河南")), 2)
    // 使用自定义分区器重新分区
    val resRDD: RDD[(String, String)] = rdd.partitionBy(new MyPartitoner(4))
    resRDD.mapPartitionsWithIndex{
      case (index, datas) => {
        println(index + "--->" + datas.mkString(","))
        datas
      }
    }.collect()
    sc.stop()
  }
}

// 自定义分区器
class MyPartitoner(partitons: Int) extends Partitioner {
  // 分区数量
  override def numPartitions: Int = partitons

  // 自定义分区逻辑
  override def getPartition(key: Any): Int = {
    val strKey: String = key.asInstanceOf[String]
    // 返回分区号
    if (strKey.startsWith("136")) 0
    else if (strKey.startsWith("137")) 1
    else if (strKey.startsWith("138")) 2
    else 3
  }
}

按照Value分区

  • RDD通过map映射,把value和key的位置调换。

14.读取文件,实现WordCount功能。(使用不同的算子实现,至少3种方式)

方式一

    val rdd: RDD[String] = sc.textFile("D:\\develop\\workspace\\bigdata2021\\spark2021\\input\\word.txt")
     // 方式一
    val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)

方式二

	val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).groupByKey().map {
      case (key, datas) => {
        (key, datas.size)
      }
   	}

方式三

	val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).groupBy(word => word).map {
      case (key, datas) => {
        (key, datas.size)
      }
    }

方式四

    val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).aggregateByKey(0)(_+_,_+_)

方式五

	val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).foldByKey(0)(_+_)

方式六

	val wordCountRDD: RDD[(String, Int)] = rdd.flatMap(_.split(" ")).map((_, 1)).combineByKey(
      // 转换数据结构
      elem => elem,
      // 分区内计算规则
      (a: Int, b: Int) => {
        a + b
      },
      // 分区间计算规则
      (a: Int, b: Int) => {
        a + b
      }
    )

15.说说你对RDD血缘关系的理解

  • RDD通过转换算子生成一系列的RDD,Spark会记录每个RDD之间的依赖关系,此关系称为血缘,可以通过toDebugString算子查看RDD之间的血缘关系。
  • Spark会根据RDD之间的血缘关系形成DAG有向无环图,根据此有向无环图,Spark可以高效的处理容错和数据的恢复工作。

16.Spark是如何进行任务切分的,请说明其中涉及到的相关概念

  • 应用:一个spark程序就是一个应用,一个应用可以有多个job。
  • job作业:触发一次action算子就是一次job,一个job可以有多个stage。
  • stage阶段:宽依赖切分不同的stage,stage的数量=宽依赖数量+1。一个stage可以有多个task。
  • task任务:每个stage的最后一个RDD的分区数量=task的数量。

17.RDD的cache和checkPoint的区别和联系

联系

  • cache和checkPoint都是对RDD中的数据做缓存,后面计算逻辑相同的RDD,可以直接从缓存中取数据,而不用重新进行计算。
  • 只有触发action算子时,才会真正的缓存。

区别

  • cache不会切断RDD的血缘关系,缓存默认存储在内存中,可以设置存储在本地磁盘上,但是随着程序运行结束,cache缓存的数据都会丢失。
  • checkpoint检查点会切断RDD的血缘关系,可以把数据存储在HDFS等高可用、可靠性高的存储系统中。
  • 为了确保数据的准确性,checkpoint检查点在第一次使用时,会根据RDD的血缘关系,从头到尾执行一遍。
  • 一般checkpoin和cache搭配来使用。

18.Spark读取HDFS文件默认的切片机制

  • 创建RDD时,会将文件路径和最小分区数minPartitions传递到HadoopRDD中。
    在这里插入图片描述

  • 调用FileInputFormat中的getSplits方法计算切片信息。
    在这里插入图片描述

  • 首先计算目标切片大小goalSize:目标切片大小 = 文件大小 / 最小分区数 。
    在这里插入图片描述

  • 再计算切片最小值minSize:max(FileInputFormat.SPLIT_MINSIZE,minSplitSize),最小值为1。
    在这里插入图片描述

  • 接着计算splitSize = Math.max(minSize, Math.min(goalSize, blockSize)),所以SplitSize一般为goalSize和blockSize两者的最小值。
    在这里插入图片描述
    在这里插入图片描述

  • 如果剩余待处理文件大小 / splitSize > 1.1,那么就切一片。
    在这里插入图片描述

19.说说你对广播变量的理解

  • 广播变量:分布式共享只读变量。
  • Spark程序在运行时,通过算子传递函数,函数中可能需要访问Driver端定义的变量,spark会为Executor端的每个task都发送一份这个变量的副本,如果变量比较大,那么就会消耗过多的Executor端的内存。
  • 如果我们把变量定义为广播变量,那么Spark只会发送一个广播变量的副本到Executor端,供所有的task访问。

20.自定义一个累加器,实现计数功能

计算以H开头的单词数量

object Spark14_Interview20 {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName(this.getClass.getName).setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd: RDD[String] = sc.makeRDD(List("Hello", "HaHa", "spark", "scala", "java", "Hive", "Hi"), 2)
    // 声明累加器
    val acc = new MyAccumulator20
    // 注册
    sc.register(acc, "myacc")
    // 使用
    rdd.foreach(datas => {
      acc.add(datas)
    })
    // 打印累加器中的值
    println(acc.value)

    sc.stop()
  }
}

// 自定义累加器  泛型为输入类型和输出类型
class MyAccumulator20 extends AccumulatorV2[String, Int] {
  var num = 0

  // 判断是否为初始值
  override def isZero: Boolean = {
    num == 0
  }

  // 复制累加器
  override def copy(): AccumulatorV2[String, Int] = {
    val MyAcc = new MyAccumulator20
    MyAcc.num = this.num
    MyAcc
  }

  // 重置累加器
  override def reset(): Unit = {
    num = 0
  }

  // 添加元素
  override def add(v: String): Unit = {
    if (v.startsWith("H")) {
      num += 1
    }
  }

  // 合并累加器
  override def merge(other: AccumulatorV2[String, Int]): Unit = {
    num = this.num + other.value
  }

  // 取值
  override def value: Int = num
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值