Spark-SQL 面试准备 1

本文主要介绍了Spark中的核心概念,包括RDD的定义及其五大特性,探讨了map、mapPartitions、foreach和foreachPartition等算子的区别,详细解释了Spark中的宽窄依赖和Stage划分,以及DAGScheduler的作用。此外,还讨论了Job的生成、有向无环图(DAG)的概念,以及WordCount示例和RDD的分类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spark Knowledge NO.1

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

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

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

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

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

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中的宽窄依赖:

img

​ 图中左边是宽依赖,父RDD的4号分区数据划分到子RDD的多个分区(一分区对多分区),这就表明有shuffle过程,父分区数据经过shuffle过程的hash分区器(也可自定义分区器)划分到子RDD。例如GroupByKey,reduceByKey,join,sortByKey等操作。

​ 图右边是窄依赖,父RDD的每个分区的数据直接到子RDD的对应一个分区(一分区对一分区),例如1号到5号分区的数据都只进入到子RDD的一个分区,这个过程没有shuffle。Spark中Stage的划分就是通过shuffle来划分。(shuffle可理解为数据的从原分区打乱重组到新的分区)如:map,filter

Spark基于lineage的容错性是指,如果一个RDD出错,那么可以从它的所有父RDD重新计算所得,如果一个RDD仅有一个父RDD(即窄依赖),那么这种重新计算的代价会非常小。

Spark基于Checkpoint(物化)的容错机制何解?在上图中,宽依赖得到的结果(经历过Shuffle过程)是很昂贵的,因此,Spark将此结果物化到磁盘上了,以备后面使用

对于join操作有两种情况,如果join操作的每个partition 仅仅和已知的Partition进行join,此时的join操作就是窄依赖;其他情况的join操作就是宽依赖;因为是确定的Partition数量的依赖关系,所以就是窄依赖,得出一个推论,窄依赖不仅包含一对一的窄依赖,还包含一对固定个数的窄依赖(也就是说对父RDD的依赖的Partition的数量不会随着RDD数据规模的改变而改变)

4. spark中如何划分stage:

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

  1. 一个 job,就是由一个 rdd 的 action 触发的动作,可以简单的理解为,当你需要执行一个 rdd 的 action 的时候,会生成一个 job。

  2. stage : stage 是一个 job 的组成单位,就是说,一个 job 会被切分成 1 个或 1 个以上的 stage,然后各个 stage 会按照执行顺序依次执行。

  3. task :即 stage 下的一个任务执行单元,一般来说,一个 rdd 有多少个partition,就会有多少个 task,因为每一个 task 只是处理一个partition 上的数据。

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

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

  6. 每个Stage里面的Task的数量是由该Stage中最后 一个RDD的Partition数量决定的;

  7. 最后一个Stage里面的任务的类型是ResultTask,前面所有其他Stage里面的任务类型都是ShuffleMapTask;

  8. 代表当前Stage的算子一定是该Stage的最后一个计算步骤;

    图解:

    img

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Q9MMWiR-1579501612033)(C:\Users\sqian\Documents\Others\images\spark003.png)]

通过上图我们可以看到,RDD_C、RDD_D、RDD_E、RDD_F都是窄依赖关系,所以这个将他们划分为Stage2,Stage1和Stage3同理。而每一个Stage其实就是一并并行的任务,就像上图,RDD_C、RDD_D、RDD_F之间有两个并行的task0和task1;RDD_E和RDD_F之间有两个并行的task2,task3,pipeline并行计算就是基于这些task的。

由此可以看出,在spark中pipeline是一个partition对应一个partition,所以在stage内部只有窄依赖,stage与stage之间是宽依赖。(pipeline详解

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

管道中的RDD会在shuffle write的时候,对RDD进行持久化的时候。

优化

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

总结:提高stage的并行度:reduceByKey(+,patition的个数) ,join(+,patition的个数)

6. 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执行。

7.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才会提交。

8.有向无环图:

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

9.Wordcount示例

img

Spark的运行架构由Driver(可理解为master)和Executor(可理解为worker或slave)组成,Driver负责把用户代码进行DAG切分,划分为不同的Stage,然后把每个Stage对应的task调度提交到Executor进行计算,这样Executor就并行执行同一个Stage的task。

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

算子的定义:RDD中定义的函数,可以对RDD中的数据进行转换和操作。下面根据算子类型的分类进行总结:

1. value型算子
从输入到输出可分为一对一(包括cache)、多对一、多对多、输出分区为输入分区自激
1)一对一,
map,简单的一对一映射,集合不变;
flatMap,一对一映射,并将最后映射结果整合;
mappartitions,对分区内元素进行迭代操作,例如过滤等,然后分区不变
glom,将分区内容转换成数据
2)多对一,
union,相同数据类型RDD进行合并,并不去重
cartesian,对RDD内的所有元素进行笛卡尔积操作
3)多对多,
groupBy,将元素通过函数生成相应的Key,然后转化为Key-value格式
4)输出分区为出入分区子集,
filter,对RDD进行过滤操作,结果分区不调整
distinct,对RDD进行去重操作,
subtract,RDD间进行减操作,去除相同数据元素
sample/takeSample 对RDD进行采样操作
5)cache,
cache,将RDD数据原样存入内存
persist,对RDD数据进行缓存操作

2. Key-Value算子
Key-Value算子大致可分为一对一,聚集,连接三类操作
1)一对一,
mapValues,针对数值对中的Value进行上面提到的map操作
2)聚集操作
combineByKey、reduceByKey、partitionBy、cogroup
3)连接
join、leftOutJoin、rightOutJoin
3. Actions算子
该算子通过SparkContext执行提交作业操作,出发RDD DAG的执行
1)foreach, 对RDD中每个元素进行操作,但是不返回RDD或者Array,只返回Unit
2)存入HDFS,saveAsTextFile,saveAsObjectFile
3)scala数据格式,collect,collectAsMap,reduceByKeyLocally, lookup, count, top, reduce, fold, aggregate

### 关于 Spark SQL 面试的常见问题及解答 #### 1. Spark SQL 的功能与特性有哪些? Spark SQL 是 Apache Spark 提供的一个模块,用于结构化数据处理。其主要特点在于能够无缝集成各种数据源,并支持标准 SQL 查询以及 DataFrame API。这使得无论是数据科学家、数据分析师还是开发工程师都能够高效地处理和分析大数据集[^1]。 #### 2. 如何优化 Spark SQL 中的大规模连接操作? 在 Spark SQL 中,连接操作是影响性能的重要环节之一。为了提高效率,可以根据具体情况进行如下调整: - 对于大表连小表的情形,推荐采用广播连接方式;此时较小表格会被分发至每一个计算节点上执行局部 join 操作,从而减少了全局范围内的数据交换量。 - 当面对两份大型资料库之间的关联需求时,则更适合运用排序合并连接策略——即先各自按 key 排序再逐条匹配记录,以此来降低网络传输成本并加快速度。 - 此外,在设计查询逻辑前还需充分考量目标数据集本身的分布状况(比如分区),力求做到负载均衡以进一步增强整体表现力[^3]. ```sql -- Example of a broadcast join in Spark SQL SELECT /*+ BROADCAST(small_table) */ * FROM large_table l JOIN small_table s ON l.key = s.key; ``` #### 3. 列式存储如何帮助改善 Spark SQL 性能? 列式存储格式(如 Parquet, ORC)相比传统行存文件具有更高的压缩率和更快的数据访问速率。因为它们允许只加载所需字段而非整行读入内存中进行过滤或聚合运算,所以特别适合用来加速基于特定属性的选择性检索任务。此外,这类布局也更容易实现向量化处理单元间的流水线作业模式,进而带来更优的整体吞吐能力。 ```scala // Enabling columnar storage format when writing data to disk with Spark df.write.format("parquet").save("/path/to/output") ``` #### 4. 广播变量和累加器有什么用途? - **广播变量**:当存在一些相对固定不变的小型辅助信息需要频繁跨集群传递给各 worker 节点使用时,可以通过设置广播变量的方式来节省带宽资源消耗。一旦创建成功之后便不可更改其内部状态,因此非常适合充当配置参数或是静态映射表的角色[^4]. - **累加器**:作为一种特殊的共享对象形式,主要用于收集来自不同进程贡献的结果值总和。典型应用场景包括但不限于错误日志汇总统计、迭代算法中间产物累积更新等场合下保持数值单调增长趋势的需求满足. ```python from pyspark import AccumulatorParam class VectorAccumulatorParam(AccumulatorParam): def zero(self, value): return [0]*len(value) def addInPlace(self, val1, val2): for i in range(len(val1)): val1[i] += val2[i] return val1 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值