1 Spark与MR的区别? (5点)
使用场景:
实时处理,spark生态更为丰富,功能更为强大、性能更佳,适用范围更广;
离线批量处理;mapreduce更简单、稳定性好、适合离线海量数据挖掘计算;速度:
基于内存,spark把运算的中间数据存放在内存,迭代计算效率更高;
mapreduce的中间结果需要落地,需要保存到磁盘,必然会有磁盘io操做,影响性能。容错:
通过弹性分布式数据集RDD来实现高效容错,RDD是只读的弹性数据集,某一部分丢失或者出错,可以通过整个数据集的计算流程的血缘关系来实现重建;
mapreduce的话容错可能只能重新计算了,成本较高。Shuffle:
Spark的中间结果不落盘;
MR的中间结果落盘;
spark就是为了解决mr落盘导致效率低下的问题而产生的,原理还是mr的原理,只是shuffle放在内存中计算了,所以效率提高很多。
2 Spark的shuffle和MR 的Shuffle的异同? (5点)
Shuffle过程本质:都是将 Map 端获得的数据使用分区机制进行划分,并将数据发送给对应的 Reducer 的过程。
总结:一个落盘,一个不落盘,spark就是为了解决mr落盘导致效率低下的问题而产生的,原理还是mr的原理,只是shuffle放在内存中计算了,所以效率提高很多。
区别:
- 从逻辑角度来讲:
Shuffle 过程就是一个 GroupByKey 的过程,两者没有本质区别。
MapReduce 为了方便 GroupBy 存在于不同 partition 中,就提前对 key 进行排序。
Spark 认为很多应用不需要对 key 排序,就默认没有在 GroupBy 的过程中对 key 排序。
从数据流角度讲:
MapReduce 只能从一个 Map Stage shuffle 数据
Spark 可以从多个 Map Stages shuffle 数据
(这是 DAG 型数据流的优势,可以表达复杂的数据流操作)Shuffle write/read 实现上有一些区别:
MapReduce 是 sort-based(shuffle write和shuffle read过程都是基于key sorting)
Spark 用了sort-based 和 hash-based ,是两者的结合体从数据 fetch 与数据计算的重叠粒度来讲:
MapReduce 是粗粒度,reducer fetch 到的 records 先被放到 shuffle buffer 中休息,当 shuffle buffer 快满时,才对它们进行 combine()。
而 Spark 是细粒度,可以即时将 fetch 到的 record 与 HashMap 中相同 key 的 record 进行聚合操作。从性能优化角度来讲:
MapReduce 的 shuffle 方式单一。
Spark考虑的更全面。Spark 针对不同类型的操作、不同类型的参数,会使用不同的 shuffle write 方式
3 Spark的Shuffle 和 MR的Shuffle?
MR的Shuffle中,将拉取过来的数据会进行合并排序(merge sort ),是在磁盘上进行的,有效的控制了内存的使用,但是代价是更多的磁盘I/O
而Spark的Shuffle,在大多数情况下,拉取(fetch)的数据不需要sort,因此Spark并不在Reducer端进行merge sort,而是使用聚合(aggregator),实际上使用hashmap,来一个处理一个,存放在Partition中,为了使得内存能够容纳这个Partition,可以增加Mapper和Reducer的数量来减少Partition的大小。但是弊端是Partition多了,会带来Shuffle write时桶的write handle。所以要权衡。
4 spark基本的工作流程?
首先所有的spark应用程序都离不开sparkcontext和executor,executor负责执行任务,运行executor的节点称为worker;
1 sparkcontext由用户程序启动,是程序运行的总入口;
2 sparkcontext初始化过程中分别创建DAGScheduler进行作业调度和TaskScheduler进行任务调度,这两级调度模块;
3 sparkcontext通过集群管理与Executor通信;
4 所以一般流程是:RDD-->DAGScheduler-->taskset发送到TastScheduler-->worker执行;
DAGScheduler模块是基于任务调度的高层调度模块,它将作业拆分成具有依赖关系的多个调度阶段(通常根据shuffle来划分),每个阶段(parse)构建出一组具体的任务(taskset),然后以taskset形式提交给任务调度模块具体执行。DAGScheduler负责任务的逻辑调度,而TaskScheduler负责任务的物理调度;
与DAGScheduler交互的接口有taskScheduler和SchedulerBackend;taskScheduler的实现主要是用于DAGScheduler交互,负责具体任务的调度与运行;
SchedulerBackend的实现是与底层资源调度系统交互,配合taskScheduler实现具体任务的资源分配;
在提交任务和更新状态时,taskScheduler都会调用backend的receiveOffers函数发起一次资源调度;
Executor:任务的运行都在executor上,其中为每一个任务创建一个taskTunner类,然后交给线程池运行,具体过程:创建线程---创建任务列表---创建taskRunner---放入任务列表---提交线程池运行。
5 spark有哪几种运行模式?
1 Local:本地模式,使用n个线程
2 Local Cluster:伪分布式模式,可以开启多个虚拟节点
3 standalone 模式:部署spark到相关的节点
4 yarn模式:部署spark和yarn到相关的节点
5 mesos模式:部署spark与mesos到相关节点
6 Spark的分区器:HashPartitioner与RangePartitioner的实现?
HashPartitioner
使用key计算其hashCode,除以分区的个数取余,得到的值作为分区ID
其结果可能导致分区中的数据量不均匀,产生数据倾斜,
RangePartitioner
尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,但是分区内的元素是不能保证有序的,即就是将一点范围的数据映射到某一个分区内
两个步骤实现:
1:先从整个RDD中抽取样本数据,将样本数据排序,计算每个分区的最大key值,将其存入数组,形成一个数组(Array[key])类型的变量rangeBounds
2:判断key在数组中所处的位置,给出该key值在下一个RDD中的分区ID下标;该分区器要求RDD中的KEY类型必须是可以排序的。
7 SparkSQL的3种Join实现方式?
因为Join操作是对两个表中key值相同的记录进行连接,在SparkSQL中,对两个表做Join最直接的方式是先根据key分区,再在每个分区中把key值相同的记录拿出来做连接操作。但这样就不可避免地涉及到shuffle,而shuffle在Spark中是比较耗时的操作,我们应该尽可能的设计Spark应用使其避免大量的shuffle。
SparkSQL中对于Join的实现,常见的3种:
- Broadcast join
当维度表和事实表进行Join操作时,为了避免shuffle,我们可以将大小有限的维度表的全部数据分发到每个节点上,供事实表使用。executor存储维度表的全部数据,一定程度上牺牲了空间,换取shuffle操作大量的耗时,这在SparkSQL中称作Broadcast Join
缺点:
这个方案只能用于广播较小的表,否则数据的冗余传输就远大于shuffle的开销
存在driver端的开销(被广播的表首先被collect到driver段,然后被冗余分发到每个executor上,所以当表比较大时,采用broadcast join会对driver端和executor端造成较大的压力。)
- Shuffle Hash join
通过分区的形式将大批量的数据划分成n份较小的数据集进行并行计算。
利用key相同必然分区相同的这个原理,SparkSQL将较大表的join分而治之,先将表划分成n个分区,再对两个表中相对应分区的数据分别进行Hash Join,这样即在一定程度上减少了driver广播一侧表的压力,也减少了executor端取整张被广播表的内存消耗。
Shuffle Hash Join分为两步:
对两张表分别按照join keys进行重分区,即shuffle,目的是为了让有相同join keys值的记录分到对应的分区中
对对应分区中的数据进行join,此处先将小表分区构造为一张hash表,然后根据大表分区中记录的join keys值拿出来进行匹配
我们可以看到,在一定大小的表中,SparkSQL从时空结合的角度来看,将两个表进行重新分区,并且对小表中的分区进行hash化,从而完成join。在保持一定复杂度的基础上,尽量减少driver和executor的内存压力,提升了计算时的稳定性。
- Sort Merge Sort
当两个表都非常大时,SparkSQL采用了一种全新的方案来对表进行Join,即Sort Merge Join。这种实现方式不用将一侧数据全部加载后再进行hash join,但需要在join前将数据排序
首先将两张表按照join keys进行了重新shuffle,保证join keys值相同的记录会被分在相应的分区。分区后对每个分区内的数据进行排序,排序后再对相应的分区内的记录进行连接
两个序列都是有序的,从头遍历,碰到key相同的就输出;如果不同,左边小就继续取左边,反之取右边。
可以看出,无论分区有多大,Sort Merge Join都不用把某一侧的数据全部加载到内存中,而是即用即取即丢,从而大大提升了大数据量下sql join的稳定性。