spark面试题总结(大数据面试)

Spark概述

Spark是一种快速、通用、可扩展的大数据分析引擎。
Spark是基于内存计算的大数据并行计算框架。Spark基于内存计算,提高了在大数据环境下数据处理实时性,同时保证了高容错性和高可伸缩性,允许用户将Spark部署在大量廉价硬件之上,形成集群。

1、核心组件

SparkCore: 核心部分 包含Spark基本功能(任务调度 内存管理 容错机制等)

SparkSQL: Spark中交互式处理模块

SparkStreaming: Spark中流式数据处理的模块

SparkMLib:Spark机器学习相关模块 => Mahout

SparkGraphX: Spark中图形计算的模块

SparkManagers:集群管理 (HadoopYARN、ApacheMesos、Spark自带的单独调度器)

2、spark与mapreduce的比较

MR代码繁琐,只能支持map和reduce方法。执行效率低下
不适合迭代多次、交互式、流式的处理。
Spark计算的核心思路就是将数据集缓存在内存中加快读取速度,Spark的中间结果放到内存中,一次创建数据集,可以多次迭代运算,减少IO开销。适合运算比较多的ML和DL。
Hadoop MapReduce将每个计算任务都划分为Map、Shuffle和Reduce三个阶段,Map的输入和输出要读写磁盘,Reduce的输入和输出也要读写磁盘,这对于通过递归迭代算法来解决的问题,如机器学习和数据挖掘,无疑在性能上产生很大的影响。
Spark内存级计算模型
Spark的DAG(有向无环图作业),Spark实现了非常精致的作业调度系统,这是Spark的精髓所在

Spark在计算模型和调度上比MR做了更多的优化,不需要过多地和磁盘交互。

1、Spark计算比MapReduce快的根本原因在于DAG计算模型。DAG相比Hadoop的MapReduce在大多数情况下可以减少shuffle次数。spark遇到宽依赖才会出现shuffer,通常每次MapReduce都会有一次shuffer;DAG 相当于改进版的 MapReduce,可以说是由多个 MapReduce 组成,当数据处理流程中存在多个map和多个Reduce操作混合执行时,MapReduce只能提交多个Job执行,而Spark可以只提交一次application即可完成。

2、MapReduce 每次shuffle 操作后,必须写到磁盘,然后每次计算都需要从磁盘上读书数据,磁盘上的I/O开销比较大。spark的Executor中有一个BlockManager存储模块,会将内存和磁盘共同作为存储设备,当需要多轮迭代计算时,可以将中间结果存储到这个存储模块里,下次需要时,就可以直接读该存储模块里的数据,而不需要读写到HDFS等文件系统里,因而有效减少了IO开销;还有一点就是spark的RDD数据结构,RDD在每次transformation后并不立即执行,而且action后才执行,有进一步减少了I/O操作。

3、MR它必须等map输出的所有数据都写入本地磁盘文件以后,才能启动reduce操作,因为mr要实现默认的根据Key的排序!所以要排序肯定得写完所有数据,才能排序,然后reduce来拉取。但是spark不需要,spark默认情况下,是不会对数据进行排序的。因此shufflemaptask每写入一点数据,resulttask就可以拉取一点数据,然后在本地执行我们定义的聚合函数和算子,进行计算.

4、利用多线程来执行具体的任务(Hadoop MapReduce采用的是进程模型),减少任务的启动和切换开销;

3、spark的RDD与DataFrame以及Dataset的区别:

1、基本数据结构RDD:是弹性分布式数据集。

(1)RDD特点

1)弹性:RDD的每个分区在spark节点上存储时默认是放在内存中的,若内存存储不下,则存储在磁盘中。

2)分布性:每个RDD中的数据可以处在不同的分区中,而分区可以处在不同的节点中.

3)容错性:当一个RDD出现故障时,可以根据RDD之间的依赖关系来重新计算出发生故障的RDD.

(2)RDD与DataFrame以及DataSet的区别

1)RDD

a、具有面向对象的风格,是一组表示数据的Java或Scala对象,编译时类型安全,方便处理非结构化数据。

b、处理结构化数据比较麻烦;默认采用的是java序列号方式,序列化性能开销大,而且数据存储在java堆内存中,导致gc比较频繁

2)DataFrame:

a、是一个按指定列组织的分布式数据集合。类似于表。处理结构化数据方便;可以将数据序列化为二进制格式,数据保存在堆外内存中,可以减少了gc次数。

b、不支持编译时类型安全,若结构未知,则不能操作数据。不具有面向对象风格。

3)DataSet

a、表示行(row)的JVM对象或行对象集合形式的数据,在编译时检查类型安全。方便处理结构化和非结构化数据。采用堆外内存存储,gc友好

4、spark的算子

(1)transform算子:map转换算子,filter筛选算子,flatmap,groupByKey,reduceByKey,sortByKey,join,cogroup,combinerByKey。

(2)action算子:reduce,collect,count,take,aggregate,countByKey。

transformation是得到一个新的RDD,方式很多,比如从数据源生成一个新的RDD,从RDD生成一个新的RDD,action是得到一个值,或者一个结果(直接将RDDcache到内存中)所有的transformation都是采用的懒策略,就是如果只是将transformation提交是不会执行计算的,计算只有在action被提交的时候才被触发。

(3)map与mapPartitions的区别

1)map是对rdd中的每一个元素进行操作;mapPartitions则是对rdd中的每个分区的迭代器进行操作

2)假如是普通的map,若一个partition中有1万条数据。那么map中的方法要执行和计算1万次。若是MapPartitions,一个task仅仅会执行一次function,此function一次接收所有的partition数据,执行一次即可,性能比较高。SparkSql或DataFrame默认会对程序进行mapPartition的优化。

3)普通的map操作通常不会导致内存的OOM异常,因为可以将已经处理完的1千条数据从内存里面垃圾回收掉。 但是MapPartitions操作,对于大量数据来说,将一个partition的数据一次传入一个function以后,那么可能一下子内存不够,但是又没有办法去腾出内存空间来,可能就OOM,内存溢出。

(4)treeReduce与reduce的区别

1)treeReduce:是在reduce的时候,先在自己的本地节点分区进行本地聚合一下,然后在进行全局聚合,相当于预处理.

2)reduce:是在reduce的时候,没有本地聚合,直接返回给driver端。

(5)coalesce与repartition的区别

1)coalesce 与 repartition 都是对RDD进行重新划分,repartition只是coalesce接口中参数shuffle为true的实现。

2)若coalesce中shuffle为false时,传入的参数大于现有的分区数目,RDD的分区数不变,也就是说不经过shuffle,是无法将RDD的分区数变多的。

3)若存在过多的小任务的时候,可以通过coalesce方法,收缩合并分区,减少分区的个数,减小任务调度成本,尽量避免shuffle,这样会比repartition效率高。

(6)reduceByKey与groupByKey的区别:

pairRdd.reduceByKey(+).collect.foreach(println)等价于pairRdd.groupByKey().map(t => (t._1,t._2.sum)).collect.foreach(println)

reduceByKey的结果:(hello,2)(world,3) groupByKey的结果:(hello,(1,1))(world,(1,1,1))

使用reduceByKey()的时候,会对同一个Key所对应的value进行本地聚合,然后再传输到不同节点的节点。而使用groupByKey()的时候,并不进行本地的本地聚合,而是将全部数据传输到不同节点再进行合并,groupByKey()传输速度明显慢于reduceByKey()。虽然groupByKey().map(func)也能实现reduceByKey(func)功能,但是,优先使用reduceByKey(func).

(7)spark的cache和persist的区别:

1)计算流程DAG特别长,服务器需要将整个DAG计算完成得出结果,若计算流程中突然中间算出的数据丢失了,spark又会根据RDD的依赖关系重新计算,这样会浪费时间,为避免浪费时间可以将中间的计算结果通过cache或者persist放到内存或者磁盘中

2)cache最终调用了persist方法,默认的存储级别仅是存储内存中的;persist是最根本的底层函数,有多个存储级别,executor执行时,60%用来缓存RDD,40%用来存放数据.

5、DAG叫做有向无环图

原始的RDD通过依赖关系形成了DAG,根据RDD之间依赖类型不同可以将DAG划分成不同的Stage(调度阶段)。对于窄依赖,partition的转换处理在一个Stage中完成计算。对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分Stage的依据。

6、spark如何从HDFS中读取数据(参数MR的分片)

Spark从HDFS读入文件的分区数默认等于HDFS文件的块数(blocks),HDFS中的block是分布式存储的最小单元。如果我们上传一个30GB的非压缩的文件到HDFS,HDFS默认的块容量大小128MB,因此该文件在HDFS上会被分为235块(30GB/128MB);Spark读取SparkContext.textFile()读取该文件,默认分区数等于块数即235。

(1)读取文件生成RDD时

1)从本地文件读取生成RDD:rdd的分区数 = max(本地file的分片数, sc.defaultMinPartitions)

2)从HDFS上读取文件生成RDD:rdd的分区数 = max(hdfs文件的block数目, sc.defaultMinPartitions)

(2)通过RDD生成时:

1)分区的默认个数等于spark.default.parallelism的指定值

2)根据父rdd的reduceTask数量

7、spark的checkpoint操作

checkpoint的意思就是建立检查点,类似于快照,若DAG计算流程特别长,则需要将整个DAG计算完成得出结果,但是如果中间计算出的数据出错,spark又会根据RDD的依赖关系重新计算,这样子很费性能;当然我们可以将中间的计算结果通过cache或者persist放到内存或者磁盘中,但是这样也不能保证数据完全不会丢失,存储的这个内存出问题了或者磁盘坏了,也会导致spark从头再根据RDD计算一遍,所以就有了checkpoint,其中checkpoint的作用就是将DAG中比较重要的中间数据做一个检查点将结果存储到一个高可用的地方(通常这个地方就是HDFS里面)

8、spark广播变量和累加器

(1)广播变量:广播变量只能在Driver定义,且在Exector端不可改变。当在Executor端用到了Driver变量而不使用广播变量,那么在每个Executor中有多少task就有多少Driver端变量副本。如果使用广播变量,则在每个Executor端中只有一份Driver端的变量副本,减少了executor端的备份,节省了executor的内存,同时减少了网络传输.

1、广播变量的创建:广播变量的创建发生在Driver端,当调用b=sc.broadcast(URI)来创建广播变量时,会把该变量的数据切分成多个数据块,保存到driver端的BlockManger中,使用的存储级别是:MEMORY_AND_DISK_SER。广播变量的值必须是本地的可序列化的值,不能是RDD。广播变量一旦创建就不应该再修改,这样可以保证所有的worker节点上的值是一致的。

2、广播变量的读取:b.value(),广播变量的读取也是懒加载的,此时广播变量的数据只在Driver端存在,只有在Executor端需要广播变量时才会去加载。加载后,首先从Executor本地的BlockManager中读取广播变量的数据,若存在就直接获取。只要有一个worker节点的Executor从Driver端获取到了广播变量的数据,则其他的Executor就不需要从Driver端获取了。

(2)累加器:Accumulator则可以让多个task共同操作一份变量,主要可以进行累加操作。Accumulator是存在于Driver端的,集群上运行的task进行Accumulator的累加,随后把值发到Driver端,在Driver端汇总。Accumulator只提供了累加的功能,但是却给我们提供了多个task对于同一个变量并行操作的功能,但是task只能对Accumulator进行累加操作,不能读取它的值,只有Driver端可以读取Accumulator的值。

注意:比较经典的应用场景是用来在Spark Streaming应用中记录某些事件的数量。

9、task之间的内存分配:

为了更好地使用使用内存,Executor 内运行的 Task 之间共享着 Execution 内存。

(1)Spark 内部维护了一个 HashMap 用于记录每个 Task 占用的内存。当 Task 需要在 Executor 中申请内存时,先判断 HashMap 里面是否维护着这个 Task 的内存使用情况,如果没有,则将 TaskId 为 key,内存使用量 value为0 加入到 HashMap 里面。

(2)之后为这个 Task 申请 numBytes 内存,如果 Executor 内存区域正好有大于 numBytes 的空闲内存,则在 HashMap 里面将当前 Task 使用的内存加上 numBytes,然后返回;如果当前 Executor 内存区域无法申请到每个 Task 最小可申请的内存,则当前 Task 被阻塞,直到有其他任务释放了足够的执行内存,该任务才可以被唤醒。

(3)每个 Task 可以使用 Execution 内存大小范围为 1/2N ~ 1/N,其中 N 为当前 Executor 内正在运行的 Task 个数。一个 Task 能够运行必须申请到最小内存为 (1/2N * Execution 内存);当 N = 1 的时候,Task 可以使用全部的 Execution 内存。比如如果 Execution 内存大小为 10GB,当前 Executor 内正在运行的 Task 个数为5,则该 Task 可以申请的内存范围为 10 / (2 * 5) ~ 10 / 5,也就是 1GB ~ 2GB的范围。

10、spark与MapReduce的shuffle的区别:

(1)相同点:都是将 mapper(Spark 里是 ShuffleMapTask)的输出进行 partition,不同的 partition 送到不同的 reducer(Spark里reducer 可能是下一个 stage 里的 ShuffleMapTask,也可能是 ResultTask)

(2)不同点:

1)MapReduce默认是排序的,spark默认不排序,除非使用sortByKey算子。

2)MapReduce可以划分成split,map()、spill、merge、shuffle、sort、reduce()等阶段,spark没有明显的阶段划分,只有不同的stage和算子操作。

3)MR落盘,Spark不落盘,spark可以解决mr落盘导致效率低下的问题。

11、基于yarn运行的基本流程

(1)首先通过spark-submit向yarn提交Application应用,ResouceManager选择一个NodeManager为Application启动ApplicationMaster。

(2)ApplicationMaster向ResouceManager注册和申请Container,ResouceManager收到ApplicationMaster的请求后,使用自己的资源调度算法为applicationMaster分配多个Container。

(3)ApplicationMaster在不同的Container中启动executor,executor启动之后会反向注册到ApplicationMaster;

(4)随后初始化Sparkcontext,Sparkcontext是用户通向spark集群的入口,在初始化sparkContext的同时,会初始化DAGScheduler、TaskScheduler对象。

(5)初始化后的sparkContext对RDD的所有操作形成一个DAG有向无循环图,每执行到action操作就会创建一个job到DAGScheduler中,而job又根据RDD的依赖关系划分成多个stage,每个stage根据最后一个RDD的分区数目来创建相应数量的task,这些task形成一个taskset。

(6)DAGScheduler将taskset送到taskscheduler中,然后taskscheduler对task进行序列化,封装到launchTask中,最后将launchTask发送到指定的executor中。

(7)executor接收到了TaskScheduler发送过来的launchTask 时,会对launchTask 进行反序列化,封装到一个TaskRunner 中,然后从executor线程池中获取一个线程来执行指定的任务.

(8)最终当所有的task任务完成之后,整个application执行完成,关闭sparkContext对象。

12、spark运行模式的类型

(1)本地模式:master和worker分别运行在一台机器的不同进程上,不会启动executor,由SparkSubmit进程生成指定数量的线程数来执行任务,启动多少个线程取决于local的参数:local/只启动一个线程,local[k]启动k个线程,local[*]启动跟CPU数目相同的线程。

(2)standalone模式:standalone模式既独立模式,自带完整服务,可单独部署到一个集群中,无需依赖其他任何资源管理系统,只支持FIFO调度器。在standalone模式中,没有AM和NM的概念,也没有RM的概念,用户节点直接与master打交道,由driver负责向master申请资源,并由driver进行资源的分配和调度等等。

(3)基于yarn模式:yarn-cluster和yarn-client模式,区别在于driver端启动在本地(client),还是在Yarn集群内部的AM中(cluster)

1)yarn-client:Driver是运行在本地客户端,它的AM只是作为一个Executor启动器。负责调度Application,会与yarn集群产生大量的网络传输。好处是,执行时可以在本地看到所有的log,便于调试。所以一般用于测试环境。

2)yarn-cluster:driver运行在NodeManager,每次运行都是随机分配到NM机器上去,不会产生大量的网络传输。缺点就是本地提交后看不到log,只能通过yarn application-logs application id命令来查看,比较麻烦。

13、spark的数据倾斜

1、数据倾斜的现象

(1)大部分的task执行的特别快,剩下的几个task执行的特别慢.

(2)运行一段时间后,其他task都已经执行完成,但是有的task可能会出现OOM异常。

2、数据倾斜的原因及其后果:

(1)根本原因是某个Key所对应的数据特别多,同一个key所对应的数据进入同一个reduce中,而其他的reduce中数据特别少。

(2)后果:某些任务执行特别慢,有的task可能会出现OOM异常,因为task的所分配的数据量太大,而且task每处理一条数据还要创建大量的对象,内存存储不下.

3、如何定位数据倾斜

就是看哪些地方用了会产生shuffle的算子,distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition。

4、解决数据倾斜的方法

(1)若是数据分区太少,导致部分分区中数据量相对较大,产生轻度的数据倾斜,此时增加分区数即可解决。

(2)某个Key特别多,增大分区也无效。

1)数据倾斜的类型。

a、map端的数据倾斜:map端的主要功能是从磁盘中将数据读入内存。在map端读数据时,由于读入数据的文件大小分布不均匀,因此会导致有些map读取和处理的数据特别多,而有些map处理的数据特别少,造成map端长尾。
   1.上游表文件的大小特别不均匀,并且小文件特别多(读取的记录数少),导致当前表map端读取的数据分布不均匀,引起长尾。

解决方案:可以合并上游小文件,同时调节本节点的小文件的参数来进行优化。

2.Map端做聚合时,由于某些map读取文件的某个值特别多(某些文件记录数特别多)而引起长尾。

解决方案:来打乱数据分布,使数据尽可能分布均匀。

2)reduce端解决数据倾斜的方法:

a、聚合源数据:在数据的源头将数据聚合成一个key对应多个value值.这样在进行操作时就可能不会出现shuffle过程.

b、将导致数据倾斜的key提取出来,若是key对应的null或者无效数据,就将其删除,若是正常的数据,就将其单独处理,再与正常处理的数据进行union操作.

c、对key添加随机值,操作后去掉随机值,再操作一次。将原始的 key 转化为 key + 随机值(例如Random.nextInt),对数据进行操作后将 key + 随机值 转成 key.

14、Spark中的OOM问题:

1、map类型的算子执行中内存溢出如flatMap,mapPatitions

(1)原因:map端过程产生大量对象导致内存溢出:这种溢出的原因是在单个map中产生了大量的对象导致的针对这种问题。

(2)解决方案:

1)增加堆内内存。

2)在不增加内存的情况下,可以减少每个Task处理数据量,使每个Task产生大量的对象时,Executor的内存也能够装得下。具体做法可以在会产生大量对象的map操作之前调用repartition方法,分区成更小的块传入map。

2、shuffle后内存溢出如join,reduceByKey,repartition。

shuffle内存溢出的情况可以说都是shuffle后,单个文件过大导致的。在shuffle的使用,需要传入一个partitioner,大部分Spark中的shuffle操作,默认的partitioner都是HashPatitioner,默认值是父RDD中最大的分区数.这个参数spark.default.parallelism只对HashPartitioner有效.如果是别的partitioner导致的shuffle内存溢出就需要重写partitioner代码了.

3、driver内存溢出

(1)用户在Dirver端口生成大对象,比如创建了一个大的集合数据结构。解决方案:将大对象转换成Executor端加载,比如调用sc.textfile或者评估大对象占用的内存,增加dirver端的内存

(2)从Executor端收集数据(collect)回Dirver端,建议将driver端对collect回来的数据所作的操作,转换成executor端rdd操作。

15、spark的性能优化

1、参数优化

(1)计算资源的优化:调整–executor-memory和–executor-cores的大小;core表示executor同时计算的task数,memory表示执行的内存,这两个参数过大过小都不合适,内存调大会出现内存瓶颈,内存过小会出现作业失败;core太小导致并行计算度小,计算慢,太大会引起磁盘IO瓶颈。

(2)shuffle并行度优化:shuffleReadTask并行度增大,可以设置spark.sql.shuffle.partitions值来设置并行度。数据能分配到更多的分区,减少数据倾斜默认为200。

(3)设置spark.default.parallelism=600 每个stage的默认task数量。

(4)大小表join:对于两表join,若一张表是另外一张表的2个数量级倍数大,可以考虑将小表broadcast到每一个executor,来达到降低网络传输开销优化目标;进而完全规避掉shuffle类的操作。

2、代码优化:

(1)RDD的优化:避免重复创建RDD即避免创建多个从文件读取而成的RDD,尽量复用RDD,对于多次使用的RDD需要cache或者persist;

3、算子的优化:

(1)尽量避免使用shuffle算子

1)能避免则尽量避免使用reduceByKey,join,distinct,repartition等会进行shuffle的算子

2)Broadcast小数据在map端进行join,避免shuffle

(2)使用高性能算子

1)使用reduceByKey代替groupByKey(reduceByKey在map端聚合数据)

2)使用mappartitions代替map(减少函数重复调用的计算开销)

3)使用treeReduce代替reduce(treeReduce的计算会在executor中进行本地聚合)

4)使用foreachPartitions代替foreach(原理同mapPartitions)

5)使用filter之后使用coalesce操作(目的减少分区数,减少task启动开销)

6)使用Broadcast广播变量

Executor中有一个BlockManager存储模块,会将内存和磁盘共同作为存储设备,当需要多轮迭代计算时,可以将中间结果存储到这个存储模块里,下次需要时,就可以直接读该存储模块里的数据,而不需要读写到HDFS等文件系统里,因而有效减少了IO开销;或者在交互式查询场景下,预先将表缓存到该存储系统上,从而可以提高读写IO性能。

16、spark的内存管理机制:

作为一个 JVM 进程,Executor 的内存管理建立在 JVM 的内存管理之上,Spark 对 JVM 的堆内(On-heap)空间做了详细的分配,以充分利用内存。同时,Spark 引入了堆外(Off-heap)内存,使之可以直接在工作节点的系统内存中开辟空间,进一步优化了内存的使用。

1、堆内内存:堆内内存的大小,由 Spark 应用程序启动时的 –executor-memory参数配置,分别是execution内存,storage内存,other内存。

(1)execution内存是执行内存,文档中说join,map,aggregate都在这部分内存中执行,shuffle的数据也会先缓存在这个内存中,满了再写入磁盘,能够减少磁盘IO。

(2)storage内存是存储broadcast,cache,persist数据的地方。

(3)other内存是程序执行时预留给自己的内存。

2、堆外内存:

Off-heap memory不在 JVM 内申请内存,而是调用 Java 的 unsafe 相关 API (类似于malloc()函数)直接向操作系统申请内存。堆外内存只区分 Execution 内存和 Storage 内存。

(1)优点与缺点:因为堆外内存不进过 JVM 内存管理,所以可以避免频繁的 GC,这种内存申请的缺点是必须自己编写内存申请和释放的逻辑。

(2)作用:为了进一步优化内存的使用以及提高Shuffle时排序的效率,存储经过序列化的二进制数据。

注意:无论堆内和堆外内存目前 Execution 内存和 Storage 内存可以互相共享的。也就是说,如果 Execution 内存不足,而 Storage 内存有空闲,那么 Execution 可以从 Storage 中申请空间;反之亦然.

17、spark如何分区:

分区是RDD内部并行计算的一个计算单元,RDD的数据集在逻辑上被划分为多个分片,每一个分片称为分区,分区的个数决定了并行计算的粒度,而每个分区的数值计算都是在一个任务中进行的,因此任务的个数,也是由RDD(准确来说是作业最后一个RDD)的分区数决定。spark默认分区方式是HashPartitioner.只有Key-Value类型的RDD才有分区的,非Key-Value类型的RDD分区的值是None,每个RDD的分区ID范围:0~numPartitions-1,决定这个值是属于那个分区的。

1、HashPartitioner分区:

partition = key.hashCode () % numPartitions,如果余数小于0,则用余数+分区的个数,最后返回的值就是这个key所属的分区ID。

缺点:可能导致每个分区中数据量的不均匀,极端情况下会导致某些分区拥有RDD的全部数据

2、RangePartitioner分区(范围分区):

通过抽样确定各个Partition的Key范围。首先会对采样的key进行排序,然后计算每个Partition平均包含的Key权重,最后采用平均分配原则来确定各个Partition包含的Key范围。尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,一个分区中的元素肯定都是比另一个分区内的元素小或者大;但是分区内的元素是不能保证顺序的。(计算每个Key所在Partition:当分区范围长度在128以内,使用顺序搜索来确定Key所在的Partition,否则使用二分查找算法来确定Key所在的Partition。)

3、CustomPartitioner自定义分区:

需要继承org.apache.spark.Partitioner类,sc.parallelize(List((1,‘a’),(1,‘aa’),(2,‘b’),(2,‘bb’),(3,‘c’)), 3).partitionBy(new CustomPartitioner(3))

18、sparkSQL执行的流程

SQL语句首先通过Parser模块被解析为语法树,此棵树称为Unresolved Logical Plan;Unresolved Logical Plan通过Analyzer模块借助于Catalog中的表信息解析为Logical Plan;此时,Optimizer再通过各种基于规则的优化策略进行深入优化,得到Optimized Logical Plan;优化后的逻辑执行计划依然是逻辑的,并不能被Spark系统理解,此时需要将此逻辑执行计划转换为Physical Plan。

19、sparkSQL是如何读写hive表的

(1)写到hive表

1)方式一:是利用spark Rdd的API将数据写入hdfs形成hdfs文件,之后再将hdfs文件和hive表做加载映射。

2)方式二:利用sparkSQL将获取的数据Rdd转换成dataFrame,再将dataFrame写成缓存表,最后利用sparkSQL直接插入hive表中。而对于利用sparkSQL写hive表官方有两种常见的API,第一种是利用JavaBean做映射,第二种是利用StructType创建Schema做映射

20、RDDJoin中宽依赖与窄依赖的判断

如果Join之前被调用的RDD是宽依赖(存在shuffle), 而且两个join的RDD的分区数量一致,join结果的rdd分区数量也一样,这个时候join是窄依赖,除此之外的,rdd 的join是宽依赖

21、Spark程序执行,有时候默认为什么会产生很多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程序设置生效

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

答:因为程序在运行之前,已经申请过资源了,driver和Executors通讯,不需要和master进行通讯的。

23、spark如何防止内存溢出

1)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的过程。

2)数据不平衡导致内存溢出
数据不平衡除了有可能导致内存溢出外,也有可能导致性能的问题,解决方法和上面说的类似,就是调用repartition重新分区。这里就不再累赘了。
3)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的数量。
4)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时间。

24、你如何从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范围的数据

资料来源

你可能感兴趣的:(大数据,大数据,java,spark)