史上最全的spark面试题——持续更新中

1.spark中的RDD是什么,有哪些特性?

答:RDD(Resilient Distributed Dataset)叫做分布式数据集,是spark中最基本的数据抽象,它代表一个不可变,可分区,里面的元素可以并行计算的集合

Dataset:就是一个集合,用于存放数据的

Destributed:分布式,可以并行在集群计算

Resilient:表示弹性的,弹性表示

1.RDD中的数据可以存储在内存或者磁盘中;

2.RDD中的分区是可以改变的;

五大特性:

1.A list of partitions:一个分区列表,RDD中的数据都存储在一个分区列表中

2.A function for computing each split:作用在每一个分区中的函数

3.A list of dependencies on other RDDs:一个RDD依赖于其他多个RDD,这个点很重要,RDD的容错机制就是依据这个特性而来的

4.Optionally,a Partitioner for key-value RDDs(eg:to say that the RDD is hash-partitioned):可选的,针对于kv类型的RDD才有这个特性,作用是决定了数据的来源以及数据处理后的去向

5.可选项,数据本地性,数据位置最优

2.概述一下spark中的常用算子区别(map,mapPartitions,foreach,foreachPatition)

答:map:用于遍历RDD,将函数应用于每一个元素,返回新的RDD(transformation算子)

foreach:用于遍历RDD,将函数应用于每一个元素,无返回值(action算子)

mapPatitions:用于遍历操作RDD中的每一个分区,返回生成一个新的RDD(transformation算子)

foreachPatition:用于遍历操作RDD中的每一个分区,无返回值(action算子)

总结:一般使用mapPatitions和foreachPatition算子比map和foreach更加高效,推荐使用

3.谈谈spark中的宽窄依赖:

史上最全的spark面试题——持续更新中_第1张图片

 

答:RDD和它的父RDD的关系有两种类型:窄依赖和宽依赖

宽依赖:指的是多个子RDD的Partition会依赖同一个父RDD的Partition,关系是一对多,父RDD的一个分区的数据去到子RDD的不同分区里面,会有shuffle的产生

窄依赖:指的是每一个父RDD的Partition最多被子RDD的一个partition使用,是一对一的,也就是父RDD的一个分区去到了子RDD的一个分区中,这个过程没有shuffle产生

区分的标准就是看父RDD的一个分区的数据的流向,要是流向一个partition的话就是窄依赖,否则就是宽依赖,如图所示:

史上最全的spark面试题——持续更新中_第2张图片

4.spark中如何划分stage:

答:概念:Spark任务会根据RDD之间的依赖关系,形成一个DAG有向无环图,DAG会提交给DAGScheduler,DAGScheduler会把DAG划分相互依赖的多个stage,划分依据就是宽窄依赖,遇到宽依赖就划分stage,每个stage包含一个或多个task,然后将这些task以taskSet的形式提交给TaskScheduler运行,stage是由一组并行的task组成

1.spark程序中可以因为不同的action触发众多的job,一个程序中可以有很多的job,每一个job是由一个或者多个stage构成的,后面的stage依赖于前面的stage,也就是说只有前面依赖的stage计算完毕后,后面的stage才会运行;

2.stage 的划分标准就是宽依赖:何时产生宽依赖就会产生一个新的stage,例如reduceByKey,groupByKey,join的算子,会导致宽依赖的产生;

3.切割规则:从后往前,遇到宽依赖就切割stage;

4.图解:

史上最全的spark面试题——持续更新中_第3张图片

5.计算格式:pipeline管道计算模式,piepeline只是一种计算思想,一种模式

史上最全的spark面试题——持续更新中_第4张图片

6.spark的pipeline管道计算模式相当于执行了一个高阶函数,也就是说来一条数据然后计算一条数据,会把所有的逻辑走完,然后落地,而MapReduce是1+1=2,2+1=3这样的计算模式,也就是计算完落地,然后再计算,然后再落地到磁盘或者内存,最后数据是落在计算节点上,按reduce的hash分区落地。管道计算模式完全基于内存计算,所以比MapReduce快的原因。

7.管道中的RDD何时落地:shuffle write的时候,对RDD进行持久化的时候。

8.stage的task的并行度是由stage的最后一个RDD的分区数来决定的,一般来说,一个partition对应一个task,但最后reduce的时候可以手动改变reduce的个数,也就是改变最后一个RDD的分区数,也就改变了并行度。例如:reduceByKey(_+_,3)

9.优化:提高stage的并行度:reduceByKey(_+_,patition的个数) ,join(_+_,patition的个数)

4.DAGScheduler分析:

答:概述:是一个面向stage 的调度器;

主要入参有:dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, allowLocal,resultHandler, localProperties.get)

rdd: final RDD;

cleanedFunc: 计算每个分区的函数;

resultHander: 结果侦听器;

主要功能:1.接受用户提交的job;

2.将job根据类型划分为不同的stage,记录那些RDD,stage被物化,并在每一个stage内产生一系列的task,并封装成taskset;

3.决定每个task的最佳位置,任务在数据所在节点上运行,并结合当前的缓存情况,将taskSet提交给TaskScheduler;

4.重新提交shuffle输出丢失的stage给taskScheduler;

注:一个stage内部的错误不是由shuffle输出丢失造成的,DAGScheduler是不管的,由TaskScheduler负责尝试重新提交task执行。

5.Job的生成:

答:一旦driver程序中出现action,就会生成一个job,比如count等,向DAGScheduler提交job,如果driver程序后面还有action,那么其他action也会对应生成相应的job,所以,driver端有多少action就会提交多少job,这可能就是为什么spark将driver程序称为application而不是job 的原因。每一个job可能会包含一个或者多个stage,最后一个stage生成result,在提交job 的过程中,DAGScheduler会首先从后往前划分stage,划分的标准就是宽依赖,一旦遇到宽依赖就划分,然后先提交没有父阶段的stage们,并在提交过程中,计算该stage的task数目以及类型,并提交具体的task,在这些无父阶段的stage提交完之后,依赖该stage 的stage才会提交。

6.有向无环图:

答:DAG,有向无环图,简单的来说,就是一个由顶点和有方向性的边构成的图中,从任意一个顶点出发,没有任意一条路径会将其带回到出发点的顶点位置,为每个spark job计算具有依赖关系的多个stage任务阶段,通常根据shuffle来划分stage,如reduceByKey,groupByKey等涉及到shuffle的transformation就会产生新的stage ,然后将每个stage划分为具体的一组任务,以TaskSets的形式提交给底层的任务调度模块来执行,其中不同stage之前的RDD为宽依赖关系,TaskScheduler任务调度模块负责具体启动任务,监控和汇报任务运行情况。

7.RDD是什么以及它的分类:

史上最全的spark面试题——持续更新中_第5张图片

8.RDD的操作

史上最全的spark面试题——持续更新中_第6张图片

史上最全的spark面试题——持续更新中_第7张图片

史上最全的spark面试题——持续更新中_第8张图片

史上最全的spark面试题——持续更新中_第9张图片

史上最全的spark面试题——持续更新中_第10张图片

史上最全的spark面试题——持续更新中_第11张图片

史上最全的spark面试题——持续更新中_第12张图片

9.RDD缓存:

Spark可以使用 persist 和 cache 方法将任意 RDD 缓存到内存、磁盘文件系统中。缓存是容错的,如果一个 RDD 分片丢失,可以通过构建它的 transformation自动重构。被缓存的 RDD 被使用的时,存取速度会被大大加速。一般的executor内存60%做 cache, 剩下的40%做task。

Spark中,RDD类可以使用cache() 和 persist() 方法来缓存。cache()是persist()的特例,将该RDD缓存到内存中。而persist可以指定一个StorageLevel。StorageLevel的列表可以在StorageLevel 伴生单例对象中找到。

Spark的不同StorageLevel ,目的满足内存使用和CPU效率权衡上的不同需求。我们建议通过以下的步骤来进行选择:

·如果你的RDDs可以很好的与默认的存储级别(MEMORY_ONLY)契合,就不需要做任何修改了。这已经是CPU使用效率最高的选项,它使得RDDs的操作尽可能的快。

·如果不行,试着使用MEMORY_ONLY_SER并且选择一个快速序列化的库使得对象在有比较高的空间使用率的情况下,依然可以较快被访问。

·尽可能不要存储到硬盘上,除非计算数据集的函数,计算量特别大,或者它们过滤了大量的数据。否则,重新计算一个分区的速度,和与从硬盘中读取基本差不多快。

·如果你想有快速故障恢复能力,使用复制存储级别(例如:用Spark来响应web应用的请求)。所有的存储级别都有通过重新计算丢失数据恢复错误的容错机制,但是复制存储级别可以让你在RDD上持续的运行任务,而不需要等待丢失的分区被重新计算。

·如果你想要定义你自己的存储级别(比如复制因子为3而不是2),可以使用StorageLevel 单例对象的apply()方法。

在不会使用cached RDD的时候,及时使用unpersist方法来释放它。

10.RDD共享变量:

在应用开发中,一个函数被传递给Spark操作(例如map和reduce),在一个远程集群上运行,它实际上操作的是这个函数用到的所有变量的独立拷贝。这些变量会被拷贝到每一台机器。通常看来,在任务之间中,读写共享变量显然不够高效。然而,Spark还是为两种常见的使用模式,提供了两种有限的共享变量:广播变量和累加器。

(1). 广播变量(Broadcast Variables)

– 广播变量缓存到各个节点的内存中,而不是每个 Task

– 广播变量被创建后,能在集群中运行的任何函数调用

– 广播变量是只读的,不能在被广播后修改

– 对于大数据集的广播, Spark 尝试使用高效的广播算法来降低通信成本

  1. val broadcastVar = sc.broadcast(Array(1, 2, 3))方法参数中是要广播的变量

(2). 累加器

累加器只支持加法操作,可以高效地并行,用于实现计数器和变量求和。Spark 原生支持数值类型和标准可变集合的计数器,但用户可以添加新的类型。只有驱动程序才能获取累加器的值

11.spark-submit的时候如何引入外部jar包:

在通过spark-submit提交任务时,可以通过添加配置参数来指定 

  • –driver-class-path 外部jar包
  • –jars 外部jar包

12.spark如何防止内存溢出:

  • driver端的内存溢出 
    • 可以增大driver的内存参数:spark.driver.memory (default 1g)
    • 这个参数用来设置Driver的内存。在Spark程序中,SparkContext,DAGScheduler都是运行在Driver端的。对应rdd的Stage切分也是在Driver端运行,如果用户自己写的程序有过多的步骤,切分出过多的Stage,这部分信息消耗的是Driver的内存,这个时候就需要调大Driver的内存。
  • map过程产生大量对象导致内存溢出 
    • 这种溢出的原因是在单个map中产生了大量的对象导致的,例如:rdd.map(x=>for(i <- 1 to 10000) yield i.toString),这个操作在rdd中,每个对象都产生了10000个对象,这肯定很容易产生内存溢出的问题。针对这种问题,在不增加内存的情况下,可以通过减少每个Task的大小,以便达到每个Task即使产生大量的对象Executor的内存也能够装得下。具体做法可以在会产生大量对象的map操作之前调用repartition方法,分区成更小的块传入map。例如:rdd.repartition(10000).map(x=>for(i <- 1 to 10000) yield i.toString)。 

面对这种问题注意,不能使用rdd.coalesce方法,这个方法只能减少分区,不能增加分区, 不会有shuffle的过程。

  • 数据不平衡导致内存溢出 
    • 数据不平衡除了有可能导致内存溢出外,也有可能导致性能的问题,解决方法和上面说的类似,就是调用repartition重新分区。这里就不再累赘了。
  • shuffle后内存溢出 
    • shuffle内存溢出的情况可以说都是shuffle后,单个文件过大导致的。在Spark中,join,reduceByKey这一类型的过程,都会有shuffle的过程,在shuffle的使用,需要传入一个partitioner,大部分Spark中的shuffle操作,默认的partitioner都是HashPatitioner,默认值是父RDD中最大的分区数,这个参数通过spark.default.parallelism控制(在spark-sql中用spark.sql.shuffle.partitions) , spark.default.parallelism参数只对HashPartitioner有效,所以如果是别的Partitioner或者自己实现的Partitioner就不能使用spark.default.parallelism这个参数来控制shuffle的并发量了。如果是别的partitioner导致的shuffle内存溢出,就需要从partitioner的代码增加partitions的数量。
  • standalone模式下资源分配不均匀导致内存溢出
    • 在standalone的模式下如果配置了–total-executor-cores 和 –executor-memory 这两个参数,但是没有配置–executor-cores这个参数的话,就有可能导致,每个Executor的memory是一样的,但是cores的数量不同,那么在cores数量多的Executor中,由于能够同时执行多个Task,就容易导致内存溢出的情况。这种情况的解决方法就是同时配置–executor-cores或者spark.executor.cores参数,确保Executor资源分配均匀。
  • 使用rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)代替rdd.cache()
    • rdd.cache()和rdd.persist(Storage.MEMORY_ONLY)是等价的,在内存不足的时候rdd.cache()的数据会丢失,再次使用的时候会重算,而rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)在内存不足的时候会存储在磁盘,避免重算,只是消耗点IO时间。

13.spark中cache和persist的区别:

  • cache:缓存数据,默认是缓存在内存中,其本质还是调用persist
  • persist:缓存数据,有丰富的数据缓存策略。数据可以保存在内存也可以保存在磁盘中,使用的时候指定对应的缓存级别就可以了。

14.spark分布式集群搭建的步骤:

史上最全的spark面试题——持续更新中_第13张图片

地球人都知道

  • 这里可以概述下如何搭建高可用的spark集群(HA) 
    • 主要是引入了zookeeper

15.spark中的数据倾斜的现象,原因,后果:

  • (1)、数据倾斜的现象 
    • 多数task执行速度较快,少数task执行时间非常长,或者等待很长时间后提示你内存不足,执行失败。
  • (2)、数据倾斜的原因 
    • 数据问题 
      • 1、key本身分布不均衡(包括大量的key为空)
      • 2、key的设置不合理
    • spark使用问题 
      • 1、shuffle时的并发度不够
      • 2、计算方式有误
  • (3)、数据倾斜的后果 
    • 1、spark中的stage的执行时间受限于最后那个执行完成的task,因此运行缓慢的任务会拖垮整个程序的运行速度(分布式程序运行的速度是由最慢的那个task决定的)。
    • 2、过多的数据在同一个task中运行,将会把executor撑爆。

16.spark数据倾斜的处理:

发现数据倾斜的时候,不要急于提高executor的资源,修改参数或是修改程序,首先要检查数据本身,是否存在异常数据。

  • 1、数据问题造成的数据倾斜
    • 找出异常的key 
      • 如果任务长时间卡在最后最后1个(几个)任务,首先要对key进行抽样分析,判断是哪些key造成的。 选取key,对数据进行抽样,统计出现的次数,根据出现次数大小排序取出前几个。
      • 比如: df.select(“key”).sample(false,0.1).(k=>(k,1)).reduceBykey(+).map(k=>(k._2,k._1)).sortByKey(false).take(10)
      • 如果发现多数数据分布都较为平均,而个别数据比其他数据大上若干个数量级,则说明发生了数据倾斜。
      •  
    • 经过分析,倾斜的数据主要有以下三种情况: 
      • 1、null(空值)或是一些无意义的信息()之类的,大多是这个原因引起。
      • 2、无效数据,大量重复的测试数据或是对结果影响不大的有效数据。
      • 3、有效数据,业务导致的正常数据分布。
    • 解决办法 
      • 第1,2种情况,直接对数据进行过滤即可(因为该数据对当前业务不会产生影响)。
      • 第3种情况则需要进行一些特殊操作,常见的有以下几种做法 
        • (1) 隔离执行,将异常的key过滤出来单独处理,最后与正常数据的处理结果进行union操作。
        • (2) 对key先添加随机值,进行操作后,去掉随机值,再进行一次操作。
        • (3) 使用reduceByKey 代替 groupByKey(reduceByKey用于对每个key对应的多个value进行merge操作,最重要的是它能够在本地先进行merge操作,并且merge操作可以通过函数自定义.)
        • (4) 使用map join。
    • 案例 
      • 如果使用reduceByKey因为数据倾斜造成运行失败的问题。具体操作流程如下: 
        • (1) 将原始的 key 转化为 key + 随机值(例如Random.nextInt)
        • (2) 对数据进行 reduceByKey(func)
        • (3) 将 key + 随机值 转成 key
        • (4) 再对数据进行 reduceByKey(func)
    • 案例操作流程分析: 
      • 假设说有倾斜的Key,我们给所有的Key加上一个随机数,然后进行reduceByKey操作;此时同一个Key会有不同的随机数前缀,在进行reduceByKey操作的时候原来的一个非常大的倾斜的Key就分而治之变成若干个更小的Key,不过此时结果和原来不一样,怎么破?进行map操作,目的是把随机数前缀去掉,然后再次进行reduceByKey操作。(当然,如果你很无聊,可以再次做随机数前缀),这样我们就可以把原本倾斜的Key通过分而治之方案分散开来,最后又进行了全局聚合
      • 注意1: 如果此时依旧存在问题,建议筛选出倾斜的数据单独处理。最后将这份数据与正常的数据进行union即可。
      • 注意2: 单独处理异常数据时,可以配合使用Map Join解决。
  • 2、spark使用不当造成的数据倾斜
    • 提高shuffle并行度
      • dataFrame和sparkSql可以设置spark.sql.shuffle.partitions参数控制shuffle的并发度,默认为200。
      • rdd操作可以设置spark.default.parallelism控制并发度,默认参数由不同的Cluster Manager控制。
      • 局限性: 只是让每个task执行更少的不同的key。无法解决个别key特别大的情况造成的倾斜,如果某些key的大小非常大,即使一个task单独执行它,也会受到数据倾斜的困扰。
    • 使用map join 代替reduce join
      • 在小表不是特别大(取决于你的executor大小)的情况下使用,可以使程序避免shuffle的过程,自然也就没有数据倾斜的困扰了.(详细见http://blog.csdn.net/lsshlsw/article/details/50834858、http://blog.csdn.net/lsshlsw/article/details/48694893)
      • 局限性: 因为是先将小数据发送到每个executor上,所以数据量不能太大。

17.spark中map-side-join关联优化:

将多份数据进行关联是数据处理过程中非常普遍的用法,不过在分布式计算系统中,这个问题往往会变的非常麻烦,因为框架提供的 join 操作一般会将所有数据根据 key 发送到所有的 reduce 分区中去,也就是 shuffle 的过程。造成大量的网络以及磁盘IO消耗,运行效率极其低下,这个过程一般被称为 reduce-side-join。

如果其中有张表较小的话,我们则可以自己实现在 map 端实现数据关联,跳过大量数据进行 shuffle 的过程,运行时间得到大量缩短,根据不同数据可能会有几倍到数十倍的性能提升。

何时使用:在海量数据中匹配少量特定数据

原理:reduce-side-join 的缺陷在于会将key相同的数据发送到同一个partition中进行运算,大数据集的传输需要长时间的IO,同时任务并发度收到限制,还可能造成数据倾斜。

reduce-side-join 运行图如下

史上最全的spark面试题——持续更新中_第14张图片

map-side-join 运行图如下:

史上最全的spark面试题——持续更新中_第15张图片

将少量的数据转化为Map进行广播,广播会将此 Map 发送到每个节点中,如果不进行广播,每个task执行时都会去获取该Map数据,造成了性能浪费。对大数据进行遍历,使用mapPartition而不是map,因为mapPartition是在每个partition中进行操作,因此可以减少遍历时新建broadCastMap.value对象的空间消耗,同时匹配不到的数据也不会返回。

18.kafka整合sparkStreaming问题:

  • (1)、如何实现sparkStreaming读取kafka中的数据
      • 可以这样说:在kafka0.10版本之前有二种方式与sparkStreaming整合,一种是基于receiver,一种是direct,然后分别阐述这2种方式分别是什么 
      • receiver:是采用了kafka高级api,利用receiver接收器来接受kafka topic中的数据,从kafka接收来的数据会存储在spark的executor中,之后spark streaming提交的job会处理这些数据,kafka中topic的偏移量是保存在zk中的。 
      • 基本使用:

史上最全的spark面试题——持续更新中_第16张图片

      • 还有几个需要注意的点: 
      • 在Receiver的方式中,Spark中的partition和kafka中的partition并不是相关的,所以如果我们加大每个topic的partition数量,仅仅是增加线程来处理由单一Receiver消费的主题。但是这并没有增加Spark在处理数据上的并行度.
      • 对于不同的Group和topic我们可以使用多个Receiver创建不同的Dstream来并行接收数据,之后可以利用union来统一成一个Dstream。
      • 在默认配置下,这种方式可能会因为底层的失败而丢失数据. 因为receiver一直在接收数据,在其已经通知zookeeper数据接收完成但是还没有处理的时候,executor突然挂掉(或是driver挂掉通知executor关闭),缓存在其中的数据就会丢失. 如果希望做到高可靠, 让数据零丢失,如果我们启用了Write Ahead Logs(spark.streaming.receiver.writeAheadLog.enable=true)该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如HDFS)上的预写日志中. 所以, 即使底层节点出现了失败, 也可以使用预写日志中的数据进行恢复. 复制到文件系统如HDFS,那么storage level需要设置成 StorageLevel.MEMORY_AND_DISK_SER,也就是KafkaUtils.createStream(…, StorageLevel.MEMORY_AND_DISK_SER)
      • direct:在spark1.3之后,引入了Direct方式。不同于Receiver的方式,Direct方式没有receiver这一层,其会周期性的获取Kafka中每个topic的每个partition中的最新offsets,之后根据设定的maxRatePerPartition来处理每个batch。(设置spark.streaming.kafka.maxRatePerPartition=10000。限制每秒钟从topic的每个partition最多消费的消息条数)。
  • (2) 对比这2中方式的优缺点:
    • 采用receiver方式:这种方式可以保证数据不丢失,但是无法保证数据只被处理一次,WAL实现的是At-least-once语义(至少被处理一次),如果在写入到外部存储的数据还没有将offset更新到zookeeper就挂掉,这些数据将会被反复消费. 同时,降低了程序的吞吐量。
    • 采用direct方式:相比Receiver模式而言能够确保机制更加健壮. 区别于使用Receiver来被动接收数据, Direct模式会周期性地主动查询Kafka, 来获得每个topic+partition的最新的offset, 从而定义每个batch的offset的范围. 当处理数据的job启动时, 就会使用Kafka的简单consumer api来获取Kafka指定offset范围的数据。 
      • 优点: 
      • 1、简化并行读取 
        • 如果要读取多个partition, 不需要创建多个输入DStream然后对它们进行union操作. Spark会创建跟Kafka partition一样多的RDD partition, 并且会并行从Kafka中读取数据. 所以在Kafka partition和RDD partition之间, 有一个一对一的映射关系.
      • 2、高性能 
        • 如果要保证零数据丢失, 在基于receiver的方式中, 需要开启WAL机制. 这种方式其实效率低下, 因为数据实际上被复制了两份, Kafka自己本身就有高可靠的机制, 会对数据复制一份, 而这里又会复制一份到WAL中. 而基于direct的方式, 不依赖Receiver, 不需要开启WAL机制, 只要Kafka中作了数据的复制, 那么就可以通过Kafka的副本进行恢复.
      • 3、一次且仅一次的事务机制 
        • 基于receiver的方式, 是使用Kafka的高阶API来在ZooKeeper中保存消费过的offset的. 这是消费Kafka数据的传统方式. 这种方式配合着WAL机制可以保证数据零丢失的高可靠性, 但是却无法保证数据被处理一次且仅一次, 可能会处理两次. 因为Spark和ZooKeeper之间可能是不同步的. 基于direct的方式, 使用kafka的简单api, Spark Streaming自己就负责追踪消费的offset, 并保存在checkpoint中. Spark自己一定是同步的, 因此可以保证数据是消费一次且仅消费一次。不过需要自己完成将offset写入zk的过程,在官方文档中都有相应介绍. 

*简单代码实例: 

* messages.foreachRDD(rdd=>{ 

val message = rdd.map(_._2)//对数据进行一些操作 

message.map(method)//更新zk上的offset (自己实现) 

updateZKOffsets(rdd) 

}) 

* sparkStreaming程序自己消费完成后,自己主动去更新zk上面的偏移量。也可以将zk中的偏移量保存在mysql或者redis数据库中,下次重启的时候,直接读取mysql或者redis中的偏移量,获取到上次消费的偏移量,接着读取数据。

19.利用scala语言进行排序:

1.冒泡:

史上最全的spark面试题——持续更新中_第17张图片

2.快读排序:

史上最全的spark面试题——持续更新中_第18张图片

史上最全的spark面试题——持续更新中_第19张图片

20.spark master在使用zookeeper进行HA时,有哪些元数据保存在zookeeper?

答:spark通过这个参数spark.deploy.zookeeper.dir指定master元数据在zookeeper中保存的位置,包括worker,master,application,executors.standby节点要从zk中获得元数据信息,恢复集群运行状态,才能对外继续提供服务,作业提交资源申请等,在恢复前是不能接受请求的,另外,master切换需要注意两点:

1.在master切换的过程中,所有的已经在运行的程序皆正常运行,因为spark application在运行前就已经通过cluster manager获得了计算资源,所以在运行时job本身的调度和处理master是没有任何关系的;

2.在master的切换过程中唯一的影响是不能提交新的job,一方面不能提交新的应用程序给集群,因为只有Active master才能接受新的程序的提交请求,另外一方面,已经运行的程序也不能action操作触发新的job提交请求。

21.spark master HA主从切换过程不会影响集群已有的作业运行,为什么?

答:因为程序在运行之前,已经向集群申请过资源,这些资源已经提交给driver了,也就是说已经分配好资源了,这是粗粒度分配,一次性分配好资源后不需要再关心资源分配,在运行时让driver和executor自动交互,弊端是如果资源分配太多,任务运行完不会很快释放,造成资源浪费,这里不适用细粒度分配的原因是因为任务提交太慢。

22.什么是粗粒度,什么是细粒度,各自的优缺点是什么?

答:1.粗粒度:启动时就分配好资源,程序启动,后续具体使用就使用分配好的资源,不需要再分配资源。好处:作业特别多时,资源复用率较高,使用粗粒度。缺点:容易资源浪费,如果一个job有1000个task,完成了999个,还有一个没完成,那么使用粗粒度。如果有999个资源闲置在那里,会造成资源大量浪费。

2.细粒度:用资源的时候分配,用完了就立即回收资源,启动会麻烦一点,启动一次分配一次,会比较麻烦。

23.driver的功能是什么:

答:1.一个spark作业运行时包括一个driver进程,也就是作业的主进程,具有main函数,并且有sparkContext的实例,是程序的入口;

2.功能:负责向集群申请资源,向master注册信息,负责了作业的调度,负责了作业的解析,生成stage并调度task到executor上,包括DAGScheduler,TaskScheduler。

24.spark的有几种部署模式,每种模式特点?

1)本地模式

Spark不一定非要跑在hadoop集群,可以在本地,起多个线程的方式来指定。将Spark应用以多线程的方式直接运行在本地,一般都是为了方便调试,本地模式分三类

·  local:只启动一个executor

·  local[k]:启动k个executor

·  local:启动跟cpu数目相同的 executor

2)standalone模式

分布式部署集群, 自带完整的服务,资源管理和任务监控是Spark自己监控,这个模式也是其他模式的基础,

3)Spark on yarn模式

分布式部署集群,资源和任务监控交给yarn管理,但是目前仅支持粗粒度资源分配方式,包含cluster和client运行模式,cluster适合生产,driver运行在集群子节点,具有容错功能,client适合调试,dirver运行在客户端

4)Spark On Mesos模式。官方推荐这种模式(当然,原因之一是血缘关系)。正是由于Spark开发之初就考虑到支持Mesos,因此,目前而言,Spark运行在Mesos上会比运行在YARN上更加灵活,更加自然。用户可选择两种调度模式之一运行自己的应用程序:

1)   粗粒度模式(Coarse-grained Mode):每个应用程序的运行环境由一个Dirver和若干个Executor组成,其中,每个Executor占用若干资源,内部可运行多个Task(对应多少个“slot”)。应用程序的各个任务正式运行之前,需要将运行环境中的资源全部申请好,且运行过程中要一直占用这些资源,即使不用,最后程序运行结束后,回收这些资源。

2)   细粒度模式(Fine-grained Mode):鉴于粗粒度模式会造成大量资源浪费,Spark On Mesos还提供了另外一种调度模式:细粒度模式,这种模式类似于现在的云计算,思想是按需分配。

25.Spark技术栈有哪些组件,每个组件都有什么功能,适合什么应用场景?

1)Spark core:是其它组件的基础,spark的内核,主要包含:有向循环图、RDD、Lingage、Cache、broadcast等,并封装了底层通讯框架,是Spark的基础。

2)SparkStreaming是一个对实时数据流进行高通量、容错处理的流式处理系统,可以对多种数据源(如Kdfka、Flume、Twitter、Zero和TCP 套接字)进行类似Map、Reduce和Join等复杂操作,将流式计算分解成一系列短小的批处理作业。

3)Spark sql:Shark是SparkSQL的前身,Spark SQL的一个重要特点是其能够统一处理关系表和RDD,使得开发人员可以轻松地使用SQL命令进行外部查询,同时进行更复杂的数据分析

4)BlinkDB :是一个用于在海量数据上运行交互式 SQL 查询的大规模并行查询引擎,它允许用户通过权衡数据精度来提升查询响应时间,其数据的精度被控制在允许的误差范围内。

5)MLBase是Spark生态圈的一部分专注于机器学习,让机器学习的门槛更低,让一些可能并不了解机器学习的用户也能方便地使用MLbase。MLBase分为四部分:MLlib,MLI、ML Optimizer和MLRuntime。

6)GraphX是Spark中用于图和图并行计算

26.spark中worker 的主要工作是什么?

主要功能:管理当前节点内存,CPU的使用情况,接受master发送过来的资源指令,通过executorRunner启动程序分配任务,worker就类似于包工头,管理分配新进程,做计算的服务,相当于process服务,需要注意的是:

1.worker会不会汇报当前信息给master?worker心跳给master主要只有workid,不会以心跳的方式发送资源信息给master,这样master就知道worker是否存活,只有故障的时候才会发送资源信息;

2.worker不会运行代码,具体运行的是executor,可以运行具体application斜的业务逻辑代码,操作代码的节点,不会去运行代码。

27.简单说一下hadoop和spark的shuffle相同和差异?

答:1)从 high-level 的角度来看,两者并没有大的差别。 都是将 mapper(Spark 里是 ShuffleMapTask)的输出进行 partition,不同的 partition 送到不同的 reducer(Spark 里 reducer 可能是下一个 stage 里的 ShuffleMapTask,也可能是 ResultTask)。Reducer 以内存作缓冲区,边 shuffle 边 aggregate 数据,等到数据 aggregate 好以后进行 reduce() (Spark 里可能是后续的一系列操作)。

2)从 low-level 的角度来看,两者差别不小。 Hadoop MapReduce 是 sort-based,进入 combine() 和 reduce() 的 records 必须先 sort。这样的好处在于 combine/reduce() 可以处理大规模的数据,因为其输入数据可以通过外排得到(mapper 对每段数据先做排序,reducer 的 shuffle 对排好序的每段数据做归并)。目前的 Spark 默认选择的是 hash-based,通常使用 HashMap 来对 shuffle 来的数据进行 aggregate,不会对数据进行提前排序。如果用户需要经过排序的数据,那么需要自己调用类似 sortByKey() 的操作;如果你是Spark 1.1的用户,可以将spark.shuffle.manager设置为sort,则会对数据进行排序。在Spark 1.2中,sort将作为默认的Shuffle实现。

3)从实现角度来看,两者也有不少差别。 Hadoop MapReduce 将处理流程划分出明显的几个阶段:map(), spill, merge, shuffle, sort, reduce() 等。每个阶段各司其职,可以按照过程式的编程思想来逐一实现每个阶段的功能。在 Spark 中,没有这样功能明确的阶段,只有不同的 stage 和一系列的 transformation(),所以 spill, merge, aggregate 等操作需要蕴含在 transformation() 中。

如果我们将 map 端划分数据、持久化数据的过程称为 shuffle write,而将 reducer 读入数据、aggregate 数据的过程称为 shuffle read。那么在 Spark 中,问题就变为怎么在 job 的逻辑或者物理执行图中加入 shuffle write 和 shuffle read 的处理逻辑?以及两个处理逻辑应该怎么高效实现? 

Shuffle write由于不要求数据有序,shuffle write 的任务很简单:将数据 partition 好,并持久化。之所以要持久化,一方面是要减少内存存储空间压力,另一方面也是为了 fault-tolerance。

28.Mapreduce和Spark的都是并行计算,那么他们有什么相同和区别

答:两者都是用mr模型来进行并行计算:

1)hadoop的一个作业称为job,job里面分为map task和reduce task,每个task都是在自己的进程中运行的,当task结束时,进程也会结束。 

2)spark用户提交的任务成为application,一个application对应一个sparkcontext,app中存在多个job,每触发一次action操作就会产生一个job。这些job可以并行或串行执行,每个job中有多个stage,stage是shuffle过程中DAGSchaduler通过RDD之间的依赖关系划分job而来的,每个stage里面有多个task,组成taskset有TaskSchaduler分发到各个executor中执行,executor的生命周期是和app一样的,即使没有job运行也是存在的,所以task可以快速启动读取内存进行计算。 

3)hadoop的job只有map和reduce操作,表达能力比较欠缺而且在mr过程中会重复的读写hdfs,造成大量的io操作,多个job需要自己管理关系。 

spark的迭代计算都是在内存中进行的,API中提供了大量的RDD操作如join,groupby等,而且通过DAG图可以实现良好的容错。

29.RDD机制? 

答:rdd分布式弹性数据集,简单的理解成一种数据结构,是spark框架上的通用货币。 

所有算子都是基于rdd来执行的,不同的场景会有不同的rdd实现类,但是都可以进行互相转换。 

rdd执行过程中会形成dag图,然后形成lineage保证容错性等。 从物理的角度来看rdd存储的是block和node之间的映射。

30、spark有哪些组件? 

答:主要有如下组件:

1)master:管理集群和节点,不参与计算。 

2)worker:计算节点,进程本身不参与计算,和master汇报。 

3)Driver:运行程序的main方法,创建spark context对象。 

4)spark context:控制整个application的生命周期,包括dagsheduler和task scheduler等组件。 

5)client:用户提交程序的入口。

31、spark工作机制? 

答:用户在client端提交作业后,会由Driver运行main方法并创建spark context上下文。 

执行add算子,形成dag图输入dagscheduler,按照add之间的依赖关系划分stage输入task scheduler。 task scheduler会将stage划分为task set分发到各个节点的executor中执行。

32、spark的优化怎么做? 

答: spark调优比较复杂,但是大体可以分为三个方面来进行,

1)平台层面的调优:防止不必要的jar包分发,提高数据的本地性,选择高效的存储格式如parquet,

2)应用程序层面的调优:过滤操作符的优化降低过多小任务,降低单条记录的资源开销,处理数据倾斜,复用RDD进行缓存,作业并行化执行等等,

3)JVM层面的调优:设置合适的资源量,设置合理的JVM,启用高效的序列化方法如kyro,增大off head内存等等

  1. 序列化在分布式系统中扮演着重要的角色,优化Spark程序时,首当其冲的就是对序列化方式的优化。Spark为使用者提供两种序列化方式:
  2.  
  3. Java serialization: 默认的序列化方式。
  4.  
  5. Kryo serialization: 相较于 Java serialization 的方式,速度更快,空间占用更小,但并不支持所有的序列化格式,同时使用的时候需要注册class。spark-sql中默认使用的是kyro的序列化方式。
  6. 可以在spark-default.conf设置全局参数,也可以代码中初始化时对SparkConf设置 conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") ,该参数会同时作用于机器之间数据的shuffle操作以及序列化rdd到磁盘,内存。

Spark不将Kyro设置成默认的序列化方式是因为它需要对类进行注册,官方强烈建议在一些网络数据传输很大的应用中使用kyro序列化。

如果你要序列化的对象比较大,可以增加参数spark.kryoserializer.buffer所设置的值。

如果你没有注册需要序列化的class,Kyro依然可以照常工作,但会存储每个对象的全类名(full class name),这样的使用方式往往比默认的 Java serialization 还要浪费更多的空间。

可以设置 spark.kryo.registrationRequired 参数为 true,使用kyro时如果在应用中有类没有进行注册则会报错:

如上这个错误需要添加

史上最全的spark面试题——持续更新中_第20张图片

33.选择题

二、选择题

1. Spark 的四大组件下面哪个不是 (D )

A.Spark Streaming    B. Mlib 

C Graphx    D.Spark R

 

2.下面哪个端口不是 spark 自带服务的端口 (C )

A.8080 B.4040 C.8090 D.18080

备注:8080:spark集群web ui端口,4040:sparkjob监控端口,18080:jobhistory端口

 

3.spark 1.4 版本的最大变化 (B )

A spark sql Release 版本  B .引入 Spark R 

C DataFrame D.支持动态资源分配

 

4. Spark Job 默认的调度模式 (A )

A FIFO   B FAIR   

C 无   D 运行时指定

备注:Spark中的调度模式主要有两种:FIFO和FAIR。默认情况下Spark的调度模式是FIFO(先进先出),谁先提交谁先执行,后面的任务需要等待前面的任务执行。而FAIR(公平调度)模式支持在调度池中为任务进行分组,不同的调度池权重不同,任务可以按照权重来决定执行顺序。使用哪种调度器由参数spark.scheduler.mode来设置,可选的参数有FAIR和FIFO,默认是FIFO。

 

5.哪个不是本地模式运行的条件 ( D)

A spark.localExecution.enabled=true  

B 显式指定本地运行

C finalStage 无父 Stage

D partition默认值

备注:【问题】Spark在windows能跑集群模式吗?

我认为是可以的,但是需要详细了解cmd命令行的写法。目前win下跑spark的单机模式是没有问题的。

 

【关键点】spark启动机制容易被windows的命令行cmd坑

  1、带空格、奇怪字符的安装路径,cmd不能识别。最典型的坑就是安装在Program Files文件夹下的程序,因为Program和Files之间有个空格,所以cmd竟不能识别。之前就把JDK安装在了Program Files下面,然后启动spark的时候,总是提示我找不到JDK。我明明配置了环境变量了啊?这就是所谓了《已经配置环境变量,spark 仍然找不到Java》的错误问题。至于奇怪的字符,如感叹号!,我经常喜欢用来将重要的文件夹排在最前面,但cmd命令提示符不能识别。

 

  2、是否需要配置hadoop的路径的问题——答案是需要用HDFS或者yarn就配,不需要用则不需配置。目前大多数的应用场景里面,Spark大规模集群基本安装在Linux服务器上,而自己用windows跑spark的情景,则大多基于学习或者实验性质,如果我们所要读取的数据文件从本地windows系统的硬盘读取(比如说d:\data\ml.txt),基本上不需要配置hadoop路径。我们都知道,在编spark程序的时候,可以指定spark的启动模式,而启动模式有这么三中(以python代码举例):

   (2.1)本地情况,conf = SparkConf().setMaster("local[*]") ——>也就是拿本机的spark来跑程序

   (2.2)远程情况,conf = SparkConf().setMaster("spark://remotehost:7077") ——>远程spark主机

   (2.3)yarn情况,conf = SparkConf().setMaster("yarn-client") ——>远程或本地 yarn集群代理spark

针对这3种情况,配置hadoop安装路径都有什么作用呢?(2.1)本地的情况,直接拿本机安装的spark来运行spark程序(比如d:\spark-1.6.2),则配不配制hadoop路径取决于是否需要使用hdfs。java程序的情况就更为简单,只需要导入相应的hadoop的jar包即可,是否配置hadoop路径并不重要。(2.2)的情况大体跟(2.1)的情况相同,虽然使用的远程spark,但如果使用本地数据,则运算的元数据也是从本地上传到远程spark集群的,无需配置hdfs。而(2.3)的情况就大不相同,经过我搜遍baidu、google、bing引擎,均没找到SparkConf直接配置远程yarn地址的方法,唯一的一个帖子介绍可以使用yarn://remote:8032的形式,则会报错“无法解析 地址”。查看Spark的官方说明,Spark其实是通过hadoop路径下的etc\hadoop文件夹中的配置文件来寻找yarn集群的。因此,需要使用yarn来运行spark的情况,在spark那配置好hadoop的目录就尤为重要。后期经过虚拟机的验证,表明,只要windows本地配置的host地址等信息与linux服务器端相同(注意应更改hadoop-2/etc/hadoop 下各种文件夹的配置路径,使其与windows本地一致),是可以直接在win下用yarn-client提交spark任务到远程集群的。

3、是否需要配置环境变量的问题,若初次配置,可以考虑在IDE里面配置,或者在程序本身用setProperty函数进行配置。因为配置windows下的hadoop、spark环境是个非常头疼的问题,有可能路径不对而导致无法找到相应要调用的程序。待实验多次成功率提高以后,再直接配置windows的全局环境变量不迟。

 

  4、使用Netbeans这个IDE的时候,有遇到Netbeans不能清理构建的问题。原因,极有可能是导入了重复的库,spark里面含有hadoop包,记得检查冲突。同时,在清理构建之前,记得重新编译一遍程序,再进行清理并构建。

 

  5、经常遇到WARN YarnClusterScheduler: Initial job has not accepted any resources; check your cluster UI to ensure that workers are registered and have sufficient resources资源不足无法运行的问题,添加conf.set("spark.executor.memory", "512m");语句进行资源限制。先前在虚拟机跑spark,由于本身机子性能不高,给虚拟机设置的内存仅仅2G,导致hadoop和spark双开之后系统资源严重不足。因此可以缩小每个executor的运算规模。其他资源缺乏问题的解决方法参考http://blog.sina.com.cn/s/blog_4b1452dd0102wyzo.html

 

6.下面哪个不是 RDD 的特点 (C )

A. 可分区   B 可序列化   C 可修改   D 可持久化

 

7. 关于广播变量,下面哪个是错误的 (D )

A 任何函数调用    B 是只读的  

C 存储在各个节点    D 存储在磁盘或 HDFS

 

8. 关于累加器,下面哪个是错误的 (D )

A 支持加法 B 支持数值类型 

C 可并行 D 不支持自定义类型

 

9.Spark 支持的分布式部署方式中哪个是错误的 (D )

A standalone B spark on mesos  

C spark on YARN D Spark on local

 

10.Stage 的 Task 的数量由什么决定 (A )

A Partition B Job C Stage D TaskScheduler

 

11.下面哪个操作是窄依赖 (B )

A join B filter 

C group D sort

 

12.下面哪个操作肯定是宽依赖 (C )

A map B flatMap 

C reduceByKey D sample

 

13.spark 的 master 和 worker 通过什么方式进行通信的? (D )

A http B nio C netty D Akka

备注:从spark1.3.1之后,netty完全代替 了akka

一直以来,基于Akka实现的RPC通信框架是Spark引以为豪的主要特性,也是与Hadoop等分布式计算框架对比过程中一大亮点,但是时代和技术都在演化,从Spark1.3.1版本开始,为了解决大数据块(如shuffle)的传输问题,Spark引入了Netty通信框架,到了1.6.0版本,Netty居然完全取代了Akka,承担Spark内部所有的RPC通信以及数据流传输。

那么Akka又是什么东西?从Akka出现背景来说,它是基于Actor的RPC通信系统,它的核心概念也是Message,它是基于协程的,性能不容置疑;基于scala的偏函数,易用性也没有话说,但是它毕竟只是RPC通信,无法适用大的package/stream的数据传输,这也是Spark早期引入Netty的原因。

那么Netty为什么可以取代Akka?首先不容置疑的是Akka可以做到的,Netty也可以做到,但是Netty可以做到,Akka却无法做到,原因是啥?在软件栈中,Akka相比Netty要Higher一点,它专门针对RPC做了很多事情,而Netty相比更加基础一点,可以为不同的应用层通信协议(RPC,FTP,HTTP等)提供支持,在早期的Akka版本,底层的NIO通信就是用的Netty;其次一个优雅的工程师是不会允许一个系统中容纳两套通信框架,恶心!最后,虽然Netty没有Akka协程级的性能优势,但是Netty内部高效的Reactor线程模型,无锁化的串行设计,高效的序列化,零拷贝,内存池等特性也保证了Netty不会存在性能问题。

那么Spark是怎么用Netty来取代Akka呢?一句话,利用偏函数的特性,基于Netty“仿造”出一个简约版本的Actor模型!!

 

14 默认的存储级别 (A )

A MEMORY_ONLY B MEMORY_ONLY_SER

C MEMORY_AND_DISK D MEMORY_AND_DISK_SER

备注:

//不会保存任务数据 val NONE = new StorageLevel(false, false, false, false) //直接将RDD的partition保存在该节点的Disk上 val DISK_ONLY = new StorageLevel(true, false, false, false) //直接将RDD的partition保存在该节点的Disk上,在其他节点上保存一个相同的备份 val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2) //将RDD的partition对应的原生的Java Object保存在JVM中,如果RDD太大导致它的部分partition不能存储在内存中 //那么这些partition将不会缓存,并且需要的时候被重新计算,默认缓存的级别 val MEMORY_ONLY = new StorageLevel(false, true, false, true) //将RDD的partition对应的原生的Java Object保存在JVM中,在其他节点上保存一个相同的备份 val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2) val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false) val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2) //将RDD的partition反序列化后的对象存储在JVM中,如果RDD太大导致它的部分partition不能存储在内存中 //超出的partition将被保存在Disk上,并且在需要时读取 val MEMORY_AND_DISK = new StorageLevel(true, true, false, true) //在其他节点上保存一个相同的备份 val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2) val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false) val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2) //将RDD的partition序列化后存储在Tachyon中 val OFF_HEAP = new StorageLevel(false, false, true, false)

 

15 spark.deploy.recoveryMode 不支持那种 (D )

A.ZooKeeper B. FileSystem 

D NONE D Hadoop

 

16.下列哪个不是 RDD 的缓存方法 (C )

A persist() B Cache() 

C Memory()

 

17.Task 运行在下来哪里个选项中 Executor 上的工作单元 (C )

A Driver program B. spark master 

C.worker node D Cluster manager

 

18.hive 的元数据存储在 derby 和 MySQL 中有什么区别 (B )

A.没区别 B.多会话

C.支持网络环境 D数据库的区别

备注:  Hive 将元数据存储在 RDBMS 中,一般常用 MySQL 和 Derby。默认情况下,Hive 元数据保存在内嵌的 Derby 数据库中,只能允许一个会话连接,只适合简单的测试。实际生产环境中不适用, 为了支持多用户会话,则需要一个独立的元数据库,使用 MySQL 作为元数据库,Hive 内部对 MySQL 提供了很好的支持。

内置的derby主要问题是并发性能很差,可以理解为单线程操作。

Derby还有一个特性。更换目录执行操作,会找不到相关表等

 

19.DataFrame 和 RDD 最大的区别 (B )

A.科学统计支持 B.多了 schema 

C.存储方式不一样 D.外部数据源支持

备注:

史上最全的spark面试题——持续更新中_第21张图片

上图直观体现了RDD与DataFrame的区别:左侧的RDD[Person]虽然以Person为类型参数,但Spark框架本身不了解Person类的内部结构。而右侧的DataFrame却提供了详细的结构信息,使得Spark SQL可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么。DataFrame多了数据的结构信息,即schema。RDD是分布式的Java对象的集合。DataFrame是分布式的Row对象的集合。DataFrame除了提供了比RDD更丰富的算子以外,更重要的特点是提升执行效率、减少数据读取以及执行计划的优化,比如filter下推、裁剪等。

提升执行效率: RDD API是函数式的,强调不变性,在大部分场景下倾向于创建新对象而不是修改老对象。这一特点虽然带来了干净整洁的API,却也使得Spark应用程序在运行期倾向于创建大量临时对象,对GC造成压力。在现有RDD API的基础之上,我们固然可以利用mapPartitions方法来重载RDD单个分片内的数据创建方式,用复用可变对象的方式来减小对象分配和GC的开销,但这牺牲了代码的可读性,而且要求开发者对Spark运行时机制有一定的了解,门槛较高。另一方面,Spark SQL在框架内部已经在各种可能的情况下尽量重用对象,这样做虽然在内部会打破了不变性,但在将数据返回给用户时,还会重新转为不可变数据。利用 DataFrame API进行开发,可以免费地享受到这些优化效果。

减少数据读取:分析大数据,最快的方法就是 ——忽略它。这里的“忽略”并不是熟视无睹,而是根据查询条件进行恰当的剪枝。

     上文讨论分区表时提到的分区剪 枝便是其中一种——当查询的过滤条件中涉及到分区列时,我们可以根据查询条件剪掉肯定不包含目标数据的分区目录,从而减少IO。

      对于一些“智能”数据格 式,Spark SQL还可以根据数据文件中附带的统计信息来进行剪枝。简单来说,在这类数据格式中,数据是分段保存的,每段数据都带有最大值、最小值、null值数量等 一些基本的统计信息。当统计信息表名某一数据段肯定不包括符合查询条件的目标数据时,该数据段就可以直接跳过(例如某整数列a某段的最大值为100,而查询条件要求a > 200)。

      此外,Spark SQL也可以充分利用RCFile、ORC、Parquet等列式存储格式的优势,仅扫描查询真正涉及的列,忽略其余列的数据。

史上最全的spark面试题——持续更新中_第22张图片

 为了说明查询优化,我们来看上图展示的人口数据分析的示例。图中构造了两个DataFrame,将它们join之后又做了一次filter操作。如果原封不动地执行这个执行计划,最终的执行效率是不高的。因为join是一个代价较大的操作,也可能会产生一个较大的数据集。如果我们能将filter下推到 join下方,先对DataFrame进行过滤,再join过滤后的较小的结果集,便可以有效缩短执行时间。而Spark SQL的查询优化器正是这样做的。简而言之,逻辑查询计划优化就是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程。

得到的优化执行计划在转换成物 理执行计划的过程中,还可以根据具体的数据源的特性将过滤条件下推至数据源内。最右侧的物理执行计划中Filter之所以消失不见,就是因为溶入了用于执行最终的读取操作的表扫描节点内。

对于普通开发者而言,查询优化 器的意义在于,即便是经验并不丰富的程序员写出的次优的查询,也可以被尽量转换为高效的形式予以执行。

RDD和Dataset

DataSet以Catalyst逻辑执行计划表示,并且数据以编码的二进制形式被存储,不需要反序列化就可以执行sorting、shuffle等操作。

DataSet创立需要一个显式的Encoder,把对象序列化为二进制,可以把对象的scheme映射为Spark

SQl类型,然而RDD依赖于运行时反射机制。

DataFrame和Dataset

      Dataset可以认为是DataFrame的一个特例,主要区别是Dataset每一个record存储的是一个强类型值而不是一个Row。因此具有如下三个特点:

DataSet可以在编译时检查类型

并且是面向对象的编程接口。

 

20.Master 的 ElectedLeader 事件后做了哪些操作 (D )

A. 通知 driver B.通知 worker 

C.注册 application D.直接 ALIVE

 

 

34.cache后面能不能接其他算子,它是不是action操作?

答:cache可以接其他算子,但是接了算子之后,起不到缓存应有的效果,因为会重新触发cache。

cache不是action操作

35.reduceByKey是不是action?

答:不是,很多人都会以为是action,reduce rdd是action

36.数据本地性是在哪个环节确定的?

具体的task运行在那他机器上,dag划分stage的时候确定的

37.RDD的弹性表现在哪几点?

1)自动的进行内存和磁盘的存储切换;

2)基于Lingage的高效容错;

3)task如果失败会自动进行特定次数的重试;

4)stage如果失败会自动进行特定次数的重试,而且只会计算失败的分片;

5)checkpoint和persist,数据计算之后持久化缓存

6)数据调度弹性,DAG TASK调度和资源无关

7)数据分片的高度弹性,a.分片很多碎片可以合并成大的,b.par

38.常规的容错方式有哪几种类型?

1).数据检查点,会发生拷贝,浪费资源

2).记录数据的更新,每次更新都会记录下来,比较复杂且比较消耗性能

39.RDD通过Linage(记录数据更新)的方式为何很高效?

1)lazy记录了数据的来源,RDD是不可变的,且是lazy级别的,且rDD

之间构成了链条,lazy是弹性的基石。由于RDD不可变,所以每次操作就

产生新的rdd,不存在全局修改的问题,控制难度下降,所有有计算链条

将复杂计算链条存储下来,计算的时候从后往前回溯

900步是上一个stage的结束,要么就checkpoint

2)记录原数据,是每次修改都记录,代价很大

如果修改一个集合,代价就很小,官方说rdd是

粗粒度的操作,是为了效率,为了简化,每次都是

操作数据集合,写或者修改操作,都是基于集合的

rdd的写操作是粗粒度的,rdd的读操作既可以是粗粒度的

也可以是细粒度,读可以读其中的一条条的记录。

3)简化复杂度,是高效率的一方面,写的粗粒度限制了使用场景

如网络爬虫,现实世界中,大多数写是粗粒度的场景

40.RDD有哪些缺陷?

1)不支持细粒度的写和更新操作(如网络爬虫),spark写数据是粗粒度的

所谓粗粒度,就是批量写入数据,为了提高效率。但是读数据是细粒度的也就是

说可以一条条的读

2)不支持增量迭代计算,Flink支持

41.说一说Spark程序编写的一般步骤?

答:初始化,资源,数据源,并行化,rdd转化,action算子打印输出结果或者也可以存至相应的数据存储介质,具体的可看下图:

file:///E:/%E5%AE%89%E8%A3%85%E8%BD%AF%E4%BB%B6/%E6%9C%89%E9%81%93%E7%AC%94%E8%AE%B0%E6%96%87%E4%BB%B6/qq19B99AF2399E52F466CC3CF7E3B24ED5/069fa7b471f54e038440faf63233acce/640.webp

42. Spark有哪两种算子?

答:Transformation(转化)算子和Action(执行)算子。

43. Spark提交你的jar包时所用的命令是什么?

答:spark-submit。

44. Spark有哪些聚合类的算子,我们应该尽量避免什么类型的算子?

答:在我们的开发过程中,能避免则尽可能避免使用reduceByKey、join、distinct、repartition等会进行shuffle的算子,尽量使用map类的非shuffle算子。这样的话,没有shuffle操作或者仅有较少shuffle操作的Spark作业,可以大大减少性能开销。

45. 你所理解的Spark的shuffle过程?

答:从下面三点去展开

1)shuffle过程的划分

2)shuffle的中间结果如何存储

3)shuffle的数据如何拉取过来

可以参考这篇博文:http://www.cnblogs.com/jxhd1/p/6528540.html

史上最全的spark面试题——持续更新中_第23张图片

Shuffle后续优化方向:通过上面的介绍,我们了解到,Shuffle过程的主要存储介质是磁盘,尽量的减少IO是Shuffle的主要优化方向。我们脑海中都有那个经典的存储金字塔体系,Shuffle过程为什么把结果都放在磁盘上,那是因为现在内存再大也大不过磁盘,内存就那么大,还这么多张嘴吃,当然是分配给最需要的了。如果具有“土豪”内存节点,减少Shuffle IO的最有效方式无疑是尽量把数据放在内存中。下面列举一些现在看可以优化的方面,期待经过我们不断的努力,TDW计算引擎运行地更好。

MapReduce Shuffle后续优化方向:压缩:对数据进行压缩,减少写读数据量;

  • 减少不必要的排序:并不是所有类型的Reduce需要的数据都是需要排序的,排序这个nb的过程如果不需要最好还是不要的好;
  • 内存化:Shuffle的数据不放在磁盘而是尽量放在内存中,除非逼不得已往磁盘上放;当然了如果有性能和内存相当的第三方存储系统,那放在第三方存储系统上也是很好的;这个是个大招;
  • 网络框架:netty的性能据说要占优了;
  • 本节点上的数据不走网络框架:对于本节点上的Map输出,Reduce直接去读吧,不需要绕道网络框架。

Spark Shuffle后续优化方向:Spark作为MapReduce的进阶架构,对于Shuffle过程已经是优化了的,特别是对于那些具有争议的步骤已经做了优化,但是Spark的Shuffle对于我们来说在一些方面还是需要优化的。

  • 压缩:对数据进行压缩,减少写读数据量;
  • 内存化:Spark历史版本中是有这样设计的:Map写数据先把数据全部写到内存中,写完之后再把数据刷到磁盘上;考虑内存是紧缺资源,后来修改成把数据直接写到磁盘了;对于具有较大内存的集群来讲,还是尽量地往内存上写吧,内存放不下了再放磁盘。

46. 你如何从Kafka中获取数据?

1)基于Receiver的方式

这种方式使用Receiver来获取数据。Receiver是使用Kafka的高层次Consumer API来实现的。receiver从Kafka中获取的数据都是存储在Spark Executor的内存中的,然后Spark Streaming启动的job会去处理那些数据。

2)基于Direct的方式

这种新的不基于Receiver的直接方式,是在Spark 1.3中引入的,从而能够确保更加健壮的机制。替代掉使用Receiver来接收数据后,这种方式会周期性地查询Kafka,来获得每个topic+partition的最新的offset,从而定义每个batch的offset的范围。当处理数据的job启动时,就会使用Kafka的简单consumer api来获取Kafka指定offset范围的数据

47. 对于Spark中的数据倾斜问题你有什么好的方案?

1)前提是定位数据倾斜,是OOM了,还是任务执行缓慢,看日志,看WebUI

2)解决方法,有多个方面

· 避免不必要的shuffle,如使用广播小表的方式,将reduce-side-join提升为map-side-join

·分拆发生数据倾斜的记录,分成几个部分进行,然后合并join后的结果

·改变并行度,可能并行度太少了,导致个别task数据压力大

·两阶段聚合,先局部聚合,再全局聚合

·自定义paritioner,分散key的分布,使其更加均匀

详细解决方案参考博文《Spark数据倾斜优化方法》

48.RDD创建有哪几种方式?

1).使用程序中的集合创建rdd

2).使用本地文件系统创建rdd

3).使用hdfs创建rdd,

4).基于数据库db创建rdd

5).基于Nosql创建rdd,如hbase

6).基于s3创建rdd,

7).基于数据流,如socket创建rdd

如果只回答了前面三种,是不够的,只能说明你的水平还是入门级的,实践过程中有很多种创建方式。

49.Spark并行度怎么设置比较合适

答:spark并行度,每个core承载2~4个partition,如,32个core,那么64~128之间的并行度,也就是

设置64~128个partion,并行读和数据规模无关,只和内存使用量和cpu使用

时间有关

50.Spark中数据的位置是被谁管理的?

答:每个数据分片都对应具体物理位置,数据的位置是被blockManager,无论

数据是在磁盘,内存还是tacyan,都是由blockManager管理

51.Spark的数据本地性有哪几种?

答:Spark中的数据本地性有三种:

a.PROCESS_LOCAL是指读取缓存在本地节点的数据

b.NODE_LOCAL是指读取本地节点硬盘数据

c.ANY是指读取非本地节点数据

通常读取数据PROCESS_LOCAL>NODE_LOCAL>ANY,尽量使数据以PROCESS_LOCAL或NODE_LOCAL方式读取。其中PROCESS_LOCAL还和cache有关,如果RDD经常用的话将该RDD cache到内存中,注意,由于cache是lazy的,所以必须通过一个action的触发,才能真正的将该RDD cache到内存中。

52.rdd有几种操作类型?

1)transformation,rdd由一种转为另一种rdd

2)action,

3)cronroller,crontroller是控制算子,cache,persist,对性能和效率的有很好的支持

三种类型,不要回答只有2中操作

53.Spark如何处理不能被序列化的对象?

将不能序列化的内容封装成object

54.collect功能是什么,其底层是怎么实现的?

答:driver通过collect把集群中各个节点的内容收集过来汇总成结果,collect返回结果是Array类型的,collect把各个节点上的数据抓过来,抓过来数据是Array型,collect对Array抓过来的结果进行合并,合并后Array中只有一个元素,是tuple类型(KV类型的)的。

55.Spaek程序执行,有时候默认为什么会产生很多task,怎么修改默认task执行个数?

答:1)因为输入数据有很多task,尤其是有很多小文件的时候,有多少个输入

block就会有多少个task启动;2)spark中有partition的概念,每个partition都会对应一个task,task越多,在处理大规模数据的时候,就会越有效率。不过task并不是越多越好,如果平时测试,或者数据量没有那么大,则没有必要task数量太多。3)参数可以通过spark_home/conf/spark-default.conf配置文件设置:

spark.sql.shuffle.partitions 50 spark.default.parallelism 10

第一个是针对spark sql的task数量

第二个是非spark sql程序设置生效

56.为什么Spark Application在没有获得足够的资源,job就开始执行了,可能会导致什么什么问题发生?

答:会导致执行该job时候集群资源不足,导致执行job结束也没有分配足够的资源,分配了部分Executor,该job就开始执行task,应该是task的调度线程和Executor资源申请是异步的;如果想等待申请完所有的资源再执行job的:需要将spark.scheduler.maxRegisteredResourcesWaitingTime设置的很大;spark.scheduler.minRegisteredResourcesRatio 设置为1,但是应该结合实际考虑

否则很容易出现长时间分配不到资源,job一直不能运行的情况。

57.map与flatMap的区别

map:对RDD每个元素转换,文件中的每一行数据返回一个数组对象

flatMap:对RDD每个元素转换,然后再扁平化

将所有的对象合并为一个对象,文件中的所有行数据仅返回一个数组

对象,会抛弃值为null的值

58.列举你常用的action?

collect,reduce,take,count,saveAsTextFile等

59.Spark为什么要持久化,一般什么场景下要进行persist操作?

为什么要进行持久化?

spark所有复杂一点的算法都会有persist身影,spark默认数据放在内存,spark很多内容都是放在内存的,非常适合高速迭代,1000个步骤

只有第一个输入数据,中间不产生临时数据,但分布式系统风险很高,所以容易出错,就要容错,rdd出错或者分片可以根据血统算出来,如果没有对父rdd进行persist 或者cache的化,就需要重头做。

以下场景会使用persist

1)某个步骤计算非常耗时,需要进行persist持久化

2)计算链条非常长,重新恢复要算很多步骤,很好使,persist

3)checkpoint所在的rdd要持久化persist,

lazy级别,框架发现有checnkpoint,checkpoint时单独触发一个job,需要重算一遍,checkpoint前

要持久化,写个rdd.cache或者rdd.persist,将结果保存起来,再写checkpoint操作,这样执行起来会非常快,不需要重新计算rdd链条了。checkpoint之前一定会进行persist。

4)shuffle之后为什么要persist,shuffle要进性网络传输,风险很大,数据丢失重来,恢复代价很大

5)shuffle之前进行persist,框架默认将数据持久化到磁盘,这个是框架自动做的。

60.为什么要进行序列化

序列化可以减少数据的体积,减少存储空间,高效存储和传输数据,不好的是使用的时候要反序列化,非常消耗CPU

61.介绍一下join操作优化经验?

答:join其实常见的就分为两类: map-side join 和  reduce-side join。当大表和小表join时,用map-side join能显著提高效率。将多份数据进行关联是数据处理过程中非常普遍的用法,不过在分布式计算系统中,这个问题往往会变的非常麻烦,因为框架提供的 join 操作一般会将所有数据根据 key 发送到所有的 reduce 分区中去,也就是 shuffle 的过程。造成大量的网络以及磁盘IO消耗,运行效率极其低下,这个过程一般被称为 reduce-side-join。如果其中有张表较小的话,我们则可以自己实现在 map 端实现数据关联,跳过大量数据进行 shuffle 的过程,运行时间得到大量缩短,根据不同数据可能会有几倍到数十倍的性能提升。

备注:这个题目面试中非常非常大概率见到,务必搜索相关资料掌握,这里抛砖引玉。

62.介绍一下cogroup rdd实现原理,你在什么场景下用过这个rdd?

答:cogroup的函数实现:这个实现根据两个要进行合并的两个RDD操作,生成一个CoGroupedRDD的实例,这个RDD的返回结果是把相同的key中两个RDD分别进行合并操作,最后返回的RDD的value是一个Pair的实例,这个实例包含两个Iterable的值,第一个值表示的是RDD1中相同KEY的值,第二个值表示的是RDD2中相同key的值.由于做cogroup的操作,需要通过partitioner进行重新分区的操作,因此,执行这个流程时,需要执行一次shuffle的操作(如果要进行合并的两个RDD的都已经是shuffle后的rdd,同时他们对应的partitioner相同时,就不需要执行shuffle,),

场景:表关联查询

63下面这段代码输出结果是什么?

--------------------------

def joinRdd(sc:SparkContext) {

val name= Array(

Tuple2(1,"spark"),

Tuple2(2,"tachyon"),

Tuple2(3,"hadoop")

)

val score= Array(

Tuple2(1,100),

Tuple2(2,90),

Tuple2(3,80)

)

val namerdd=sc.parallelize(name);

val scorerdd=sc.parallelize(score);

val result = namerdd.join(scorerdd);

result .collect.foreach(println);

}

--------------------------

答案:

(1,(Spark,100))

(2,(tachyon,90))

(3,(hadoop,80))

你可能感兴趣的:(Spark,面试题)