5 SparkCore面试题

主宰稳场 提交于 2020-10-04 00:24:42

SparkCore面试题

1 RDD编程

1.1 RDD算子

RDD算子详解:https://blog.csdn.net/qq_38497133/article/details/107822343

一般来说,转换算子是对一个数据集里的所有记录执行某种函数,从而使记录发生改变;执行算子通常是运行某些计算或聚合操作,并将结果返回运行 SparkContext 的驱动程序。

① 请列举 Spark 的 transformation (转换)算子(不少于 8 个),并简述功能

  •  map(func) :返回一个新的RDD,该 RDD 每一个输入元素经过 func 函数转换后组成.;
  • mapPartitions(func)
    • 类似于 map,但独立地在 RDD 的每一个分片(分区)上运行,因此在类型为 T RDD 上运行时,func 的函数类型必须是 Iterator[T] => Iterator[U]
    • 假设有 N 元素,有 M 个分区,那么 map 的函数的将被调用 N ,mapPartitions 被调用 M ,一个函数一次处理所有分区;
  • flatMap 将RDD中的每一项都进行map操作,然后再进行flat压平操作(变成一个个的单词) ;
  • reduceByKey 将RDD中的key值相同的元素的Value进行合并操作,与原RDD中的Key组成一个新的KV键值对 ;
  • groupByKey groupByKey和reduceByKey的功能一样,只不过groupByKey不是在本地进行合并,而是在最终进行合并 ;
  • sortByKey:按照key进行排序 ;

② 请列举 Spark 的 action 算子(不少于 6 个),并简述功能

  • reduce(func)通过func函数聚集(聚合)RDD中的所有元素,先聚合分区内数据,再聚合分区间数据。
  •  collect():在驱动程序中,以数组的形式返回数据集的所有元素。(类似于print查看内容)
  • first():返回RDD中的第一个元素
  • take(n):返回一个由RDDn个元素组成的数组
  • count()返回RDD中元素的个数
  • saveAsTextFile(path) 将数据集的元素作为文本文件写入本地文件系统、HDFS或任何其他hadoop支持的文件系统的给定目录中 ;
  •  foreach(func)在数据集的每一个元素上,运行函数func进行更新;

③ 请列举会引起 Shuffle 过程的 Spark 算子,并简述功能

参考链接:https://www.jianshu.com/p/918af9f1470d

各种ByKey的算子

  • reduceByKey:在一个(K,V)RDD上调用,返回一个(K,V)RDD,使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置。
  • groupByKey:groupByKey也是对每个key进行操作,但只生成一个sequence。
  • aggregateByKey:k - v对的RDD中,按keyvalue进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),将key与计算结果作为一个新的k - v对输出。
  • foldByKey:aggregateByKey的简化操作,seqopcombop相同
  • combineByKey:对相同K,把V合并成一个集合。

注意:sortByKey不会引起shuffle,因为是在分区内排序,不涉及分区间传输。

④ Spark 常用算子 reduceByKey 与 groupByKey 的区别,哪一种更具优势?

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

groupByKey:按照key进行分组,直接进行shuffle;

开发指导: 在对大数据进行复杂计算时,reduceByKey优于groupByKey 。但是需要注意是否会影响业务逻 辑。

 

1.2 RDD依赖关系

参考链接:https://blog.csdn.net/dsdaasaaa/article/details/94181269

Lineage(血统):RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。RDDLineage会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

① 如何理解 Spark 中的血统概念(RDD)?

    RDD 的最重要的特性之一就是血缘关系(Lineage ),它描述了一个 RDD 是如何从父 RDD 计算得来的。如果某个 RDD 丢失了,则可以根据血缘关系,从父 RDD 计算得来。

  • RDD Lineage 依赖方面分为两种, Narrow Dependencies Wide Dependencies ,用来高效的解决数据容错问题。
  • Narrow Dependencies 是指父 RDD 的每一个分区最多被一个子 RDD 的分区所用,表现 为一个父 RDD 的分区对应于一个子 RDD 的分区或多个父 RDD 的分区对应于一个子 RDD 的分区,也就是说一个父 RDD 的一个分区不可能对应一个子 RDD 的多个分区。 (一对一、多对一)
  •  Wide Dependencies是指子 RDD 的分区依赖于父 RDD 的多个分区或所有分区,也就是说存在一个父 RDD 的一个分区对应一个子 RDD 的多个分区。对与宽依赖种计算的输入和输出在不同的节点上,lineage 方法对与输入节点完好,而输出节点宕机时,通过重新计算,这种情况下,这种方法容错是有效的;否则无效,因为无法重试,需要向 上其祖先追溯看是否可以重试(这就是 lineage,血统的意思)。(一对多、多对多)
  • Narrow Dependencies 对于数据的重算开销要远小于 Wide Dependencies 的数据重算开销 相对而言,窄依赖的失败恢复更为高效,它只需要根据父 RDD 分区重新计算丢失的分区即可,而不需要重新计算父 RDD 的所有分区。而对于宽依赖来讲,单个结点失效,即使只是 RDD 的一个分区失效,也需要重新计算父 RDD 的所有分区,开销较大。

 

② 简述 Spark 的宽窄依赖,以及 Spark 如何划分 stage,每个stage 又根据什么决定 task 个数 ?

Spark窄宽依赖见上题所述。

此题考查的是任务划分,是面试的重点。

RDD任务切分中间分为:Application、Job、Stage和Task。Application->Job->Stage-> Task每一层都是1对n的关系。

  • Application(应用):初始化一个SparkContext即生成一个Application。一般一个spark程序就是一个任务。
  • Job(作业):一个Action算子就会生成一个Job。一个spark程序会有多个Action行动算子,所以一个Application会对应多个作业。如下图整体就是一个作业。
  • Stage(阶段):根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage。
  • Task(任务):Stage是一个TaskSet,将Stage划分的结果发送到不同的Executor执行即为一个Task。(总的任务数量是各个阶段的任务数量和。不同stage阶段的任务数量取决于在阶段最后会有几个分区,有几个分区就有几个任务。如图:stage2阶段有5个任务,stage1阶段有3个任务,一共有5+3=8个任务。)

                                                                                                                    

               一个job作业,内部分了很多阶段

1.3 RDD缓存与检查点(checkpoint)机制

① 分 别 简 述 Spark 中 的 缓 存 机 制 ( cache 和 persist ) 与checkpoint 机制,并指出两者的区别与联系

(1)缓存机制( cache 和 persist )

  •   RDD缓存的作用
    • Spark运算速度非常快的原因之一, 就是在不同操作中可以在内存中持久化或者缓存数据集 ;
    • 当持久化某个RDD后, 每一个节点都将计算分区结果保存在内存中, 对此RDD或衍生出的RDD进行的其他动作中重用 , 这使得后续的动作变得更加迅速 ;
    • 缓存是Spark构建迭代式算法和快速交互式查询的关键 ,RDD相关的持久化和缓存, 是Spark最重要的特征之一 。
  • RDD缓存之后到哪里去了?
    • 缓存在计算节点(worker)中每一个机器的内存当中 , 后续要使用到缓存的RDD时, 直接从内存中拿就可以了 。
  • RDD缓存方式的实现
    • RDD通过persist方法或者cache方法可以将前面的计算结果缓存 ;
    • 但是并不是这两个方法被调用时立即缓存, 而是触发(调用)后面的action算子时, 该RDD将会被缓存在计算节点的内存中,并供后面重用 ;
  • persist方法和cache方法的区别与联系
    • 不同点:
      • cache()方法默认调用了无参的persist();
      • cache只有一个默认的缓存级别MEMORY_ONLY ,而persist可以根据情况设置其它的缓存级别;
    • 相同点:
      • persist和cache都是延迟算子,设置之后需要用执行算子激活之后,才能真正缓存到内存中 ;
      • 经常缓存的是shuffle后的RDD
  • persist和cache(RDD缓存)的缺点

    • 把缓存信息保存在当前节点的内存中,缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,或者被覆盖 ;

    •  所以用容错机制(checkpoint)解决这个数据丢失的隐患

注意事项:

    对spark做优化时,对于是否应该对RDD进行缓存时,这个需要看你的服务器的内存情况,因为缓存是需要占用内存或者硬盘资源的,当你的内存本就不够时,建议不要盲目的使RDD缓存,不然spark的那些任务丢失,executor 失败,OOM等问题就容易出现,也是常见的spark优化问题,当然尽量避免这些问题的出现.

参考链接:https://blog.csdn.net/LL9504/article/details/100665692

 

(2) Checkpoint 容错机制

  • 检查点机制的必要性
    • RDD的检查点机制的本质是将RDD写入磁盘作为检查点,是为了避免缓存丢失重新计算带来的开销,或者lineage(血统)过长而计算时间过长造成容错成本过高。这样就不如在中间阶段做检查点容错,如果之后有结点出现问题而丢失分区,那么可以从做检查点的RDD开始重做lineage,进而可以减少开销。
  • 检查点的设置
    • shuffle后的RDD, 设置检查点;
    • 设置checkpoint的目录,可以是本地目录,也可以是HDFS上的目录。一般是在具有容错能力,高可靠的文件系统上(比如HDFS、S3等)设置一个检查点路径,用于保存检查点数据;
  • cache & persist & checkpoint的区别
  • 缓存和checkpoint设置之后的读取顺序
    • 读取顺序: cache–>checkpoint–>重新计算
  • checkpoint在什么时候操作
    • checkpoint是具体action之后,重新创建一个job来完成计算,checkpoint会产生一个新的job;
    • 设置checkpoint之后,job就有两个;
    • 一个action动作后,之前的一系统RDD就是一个job
  • RDD缓存和checkpoint操作应用场景
    • 如果计算特别耗时(耗时操作占用整个应用程序的30%),此时需要考虑缓存 ;
    • 一个计算过程其计算链条过长,可以在中间比较重要的过程设置缓存或者检查点
    • 读取大量数据操作之后(读取数据占用整个应用程序执行的30%),此时需要考虑缓存
    • shuffle操作之后,有必要将rdd数据缓存或者检查点
    • 作为最佳实践操作,一般情况在checkpoint之前会使用缓存机制cache

 

 

2 RDD编程进阶

累加器和广播变量

① 简述 Spark 里,共享变量(广播变量和累加器)的基本原理与用途

在默认情况下,当Spark在集群的多个不同节点的 多个任务上并行运行一个函数时 ,在 每个任务上都生成一个副本 。但是,有时候需要在多个任务之间共享变量,或者在任务(Task)和任务控制节点(Driver Program)之间共享变量。
为了满足这种需求,Spark提供了两种类型的变量:
1.累加器 accumulators :累加器支持在所有不同节点之间进行累加计算(比如计数或者求和)
2.广播变量 broadcast variables :广播变量用来把变量在所有节点的内存之间进行共享,在每个机器上缓存一个只读的变量,而不是为机器上的每个任务都生成一个副本。

 

广播变量的意义

    广播变量用来高效分发较大的对象。

    如果我们要在分布式计算里面分发大对象,例如:字典,集合,黑白名单等,这个都会由Driver端进行分发,一般来讲,如果这个变量不是广播变量,那么每个task就会分发一份,这在task数目十分多的情况下Driver的带宽会成为系统的瓶颈,而且会大量消耗task服务器上的资源,如果将这个变量声明为广播变量,那么只是每个executor拥有一份,这个executor启动的task会共享这个变量,节省了通信的成本和服务器的资源。

 

累加器的意义

    累加器(accumulator)是 Spark 中提供的一种分布式的变量机制,其原理类似于mapreduce,即分布式的改变,然后聚合这些改变。累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数。

     在spark应用程序中,我们经常会有这样的需求,如异常监控,调试,记录符合某特性的数据的数目,这种需求都需要用到计数器,如果一个变量不被声明为一个累加器,那么它将在被改变时不会再driver端进行全局汇总,即在分布式运行时每个task运行的只是原始变量的一个副本,并不能改变原始变量的值,但是当这个变量被声明为累加器后,该变量就会有分布式计数的功能。

参考链接: https://www.cnblogs.com/frankdeng/p/9301653.html

 

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!