大数据面试问答-Spark

1. Spark

1.1 Spark定位

"Apache Spark是一个基于内存的分布式计算框架,旨在解决Hadoop MapReduce在迭代计算和实时处理上的性能瓶颈。

1.2 核心架构

Spark架构中有三个关键角色:

Driver:解析代码生成DAG,协调任务调度(TaskScheduler)。

Executor:在Worker节点执行具体Task,通过BlockManager管理数据缓存。

Cluster Manager:支持Standalone/YARN/Kubernetes资源调度。

1.3 基础概念

1.3.1 RDD

RDD本质上是一个逻辑抽象,而非物理存储的实际数据。

RDD 不直接存储数据本身,而是通过四个核心元数据描述如何生成数据:

1.数据来源(数据块指针)

若从HDFS读取:记录文件路径、分块偏移量(如hdfs://data.log, [0-128MB])。

若从父RDD转换而来:记录依赖关系(如父RDD ID + map函数)。

2.依赖关系(Dependencies)

记录父RDD与当前RDD的依赖类型(宽/窄依赖),用于容错恢复和Stage划分。

示例:RDD2 = RDD1.filter(...).map(...) 的依赖链为 Narrow -> Narrow

3.计算函数(Compute Function)

定义从父RDD或外部数据生成当前RDD分区的逻辑(如map(func)中的func)。

惰性执行:函数不会立即执行,直到遇到Action操作触发计算。

4.分区策略(Partitioner)

决定数据在计算过程中的分布方式:
HashPartitioner:按Key哈希值分配(用于groupByKey)。
RangePartitioner:按Key范围分配(用于排序操作)。

1.3.1.1 RDD中的分区

分区的本质

分区(Partition) 是 RDD 的最小计算单元,表示数据在逻辑上的分块。

虽然 RDD 不存储实际数据,但它通过分区描述数据的分布方式(类似"目录"记录"文件"的位置)。

分区的来源

初始 RDD:从外部数据源(如 HDFS、本地文件)加载时,分区规则由数据源决定。

例如:HDFS 文件按 128MB 分块,每个块对应一个 RDD 分区。

转换后的 RDD:由父 RDD 的分区规则和转换算子类型决定。

窄依赖(如 map):子 RDD 分区与父 RDD 一一对应。

宽依赖(如 groupByKey):子 RDD 分区由 Shuffle 重新分配。

维度 分区的核心作用
并行计算 每个分区由一个 Task 处理,分区数决定并行度(如 100 个分区 → 100 个 Task)
数据本地性 调度器优先将 Task 分配到数据所在节点(避免跨节点传输)
容错 分区是容错的最小单元,丢失时只需重新计算该分区

1.3.2 宽窄依赖

窄依赖(Narrow Dependency)

父RDD的每个分区最多被一个子RDD分区依赖。(一对一

无需跨节点数据传输(数据局部性优化)。

容错成本低:若子分区数据丢失,只需重新计算对应的父分区。

示例操作:map、filter、union。

宽依赖(Wide Dependency / Shuffle Dependency)

父RDD的每个分区可能被多个子RDD分区依赖。(多对多

必须跨节点传输数据(Shuffle过程)。

容错成本高:若子分区数据丢失,需重新计算所有相关父分区。

示例操作:groupByKey、reduceByKey、join(非相同分区情况)。

维度 窄依赖 宽依赖
Stage划分 允许合并到同一Stage(流水线执行) 强制划分Stage边界(需等待Shuffle完成)
数据移动 无Shuffle,本地计算优先 需跨节点Shuffle,网络IO开销大
Stage划分 可合并操作(如map+filter合并执行) 需手动调优(如调整分区数或Partitioner)

1.3.3 算子

算子的定义

在 Spark 中,算子(Operator) 是指对 RDD 或 DataFrame 进行操作的函数。所有数据处理逻辑均通过算子组合实现,类似于烹饪中的"步骤"(如切菜、翻炒、调味)。

算子分为两类:

转换算子(Transformations):定义数据处理逻辑,生成新的 RDD(惰性执行,不立即计算)。比如mapfilter

python 复制代码
# 转换算子链(无实际计算)
rdd = sc.textFile("data.txt") \
        .filter(lambda line: "error" in line) \  # 过滤错误日志
        .map(lambda line: line.split(",")[0])    # 提取第一列

宽窄依赖与算子对照表,只涉及转换算子,行动算子不涉及宽窄依赖

依赖类型 转换算子 含义及示例 触发Shuffle?
窄依赖 map(func) 对每个元素应用函数,一对一转换。例:rdd.map(x => x*2)
窄依赖 filter(func) 过滤满足条件的元素。例:rdd.filter(x => x > 10)
窄依赖 flatMap(func) 一对多转换(输出为多个元素)。例:rdd.flatMap(x => x.split(" "))
窄依赖 mapPartitions(func) 对每个分区迭代处理(更高效)。例:rdd.mapPartitions(it => it.map(_*2))
窄依赖 union(otherRDD) 合并两个RDD,保留各自分区。例:rdd1.union(rdd2)
窄依赖 glom() 将每个分区的元素合并为一个数组。例:rdd.glom() → 得到分区数组的RDD
窄依赖 mapValues(func) 仅对键值对的Value进行转换(保留原Key的分区)。例:pairRdd.mapValues(_+1)
宽依赖 groupByKey() 按键分组,相同Key的值合并为迭代器。例:pairRdd.groupByKey()
宽依赖 reduceByKey(func) 按键聚合(本地预聚合减少Shuffle量)。例:pairRdd.reduceByKey(+)
宽依赖 join(otherRDD) 按键关联两个RDD(相同Key的值连接)。例:rdd1.join(rdd2)
宽依赖 distinct() 去重(内部用reduceByKey实现)。例:rdd.distinct()
宽依赖 repartition(num) 显式调整分区数(触发全量Shuffle)。例:rdd.repartition(100)
宽依赖 sortByKey() 按键排序(需全局Shuffle)。例:pairRdd.sortByKey()
宽依赖 cogroup(otherRDD) 对多个RDD按Key联合分组(类似SQL全外连接)。例:rdd1.cogroup(rdd2)
宽依赖 intersection(otherRDD) 求两个RDD的交集。例:rdd1.intersection(rdd2)
宽依赖 subtractByKey(otherRDD) 按Key差集(移除与另一RDD匹配的Key)。例:rdd1.subtractByKey(rdd2)

行动算子(Actions):触发实际计算并返回结果或持久化数据(立即执行)。比如collectcount

python 复制代码
# 触发计算,返回结果
result = rdd.count()  # 统计过滤后的行数
print(result)
Action 算子 功能描述
collect() 将所有数据拉取到 Driver 内存
count() 统计 RDD 元素总数
take(n) 返回前 n 个元素
saveAsTextFile(path) 将结果写入 HDFS/Local 文件系统
foreach(func) 对每个元素应用函数(无返回值)
reduce(func) 全局聚合所有元素(需可交换、可结合的聚合函数)
first() 返回第一个元素
countByKey() 统计每个 Key 的出现次数

Action 触发计算的全流程
是 否 用户编写Driver程序 定义RDD转换链 调用Action操作 DAGScheduler解析DAG 是否存在宽依赖? 划分Stage边界 合并为单个Stage 生成TaskSet并调度 Executor执行Task 结果返回Driver或写入存储

1.3.3.1 reduceByKey 与 groupByKey
算子 功能描述 输出示例(输入:[("a",1), ("a",2), ("b",3)])
groupByKey 将相同 Key 的所有 Value 合并为一个 迭代器(Iterable) [("a", [1, 2]), ("b", [3])]
reduceByKey 对相同 Key 的 Value 按指定函数进行 聚合(如求和、求最大值),直接输出最终结果 [("a", 3), ("b", 3)](假设聚合函数为 lambda x,y: x + y)

groupByKey 的执行流程

Shuffle Write:将数据按 Key 分发到不同分区,所有 Value 直接传输。

Shuffle Read:每个节点拉取对应分区的数据,合并为迭代器。

输出:生成 (Key, Iterable[Value]) 形式的键值对。

reduceByKey 的执行流程

本地预聚合(Map-side Combine):

在 Map 任务所在节点,先对相同 Key 的 Value 进行局部聚合(如求和)。

Shuffle Write:仅传输 预聚合后的结果。

Shuffle Read:对预聚合结果进行全局聚合(如二次求和)。

输出:生成 (Key, AggregatedValue) 形式的键值对。

算子 优势 劣势 适用场景
reduceByKey Shuffle 数据量小,内存占用低 仅支持聚合操作 求和、计数、极值计算
groupByKey 保留所有 Value 的完整信息 Shuffle 开销大,易引发 OOM 需全量 Value 的非聚合操作

groupByKey 的 SQL 对应形式

Spark RDD 代码:

python 复制代码
rdd.groupByKey().mapValues(list)  # 输出 (Key, [Value1, Value2, ...])

Spark SQL 代码:

sql 复制代码
SELECT key, COLLECT_LIST(value) AS values
FROM table
GROUP BY key;

reduceByKey 的 SQL 对应形式

Spark RDD 代码:

python 复制代码
rdd.reduceByKey(lambda a, b: a + b)  # 输出 (Key, Sum_Value)

Spark SQL 代码:

sql 复制代码
SELECT key, SUM(value) AS total
FROM table
GROUP BY key;

1.3.4 DAGScheduler

定义:Spark 内部调度器,将逻辑 DAG 划分为物理执行的 Stage。

核心功能:

Stage 划分:以宽依赖(Shuffle)为边界切分 DAG。

任务调度:生成 TaskSet 提交给 TaskScheduler。

容错处理:重新提交失败的 Stage。

1.3.4.1 DAG(有向无环图)

定义:表示 RDD 转换操作的依赖关系图,无循环依赖。

作用:

优化执行顺序(如合并窄依赖操作)。

支持容错(通过血统重新计算丢失分区)。
textFile RDD map RDD filter RDD reduceByKey RDD Action

DAG 的节点是 RDD ,边是 RDD 之间的 依赖关系(窄依赖或宽依赖)。

DAG 的构建逻辑:

每个转换操作生成新的 RDD,并记录其父 RDD。

Action 触发时,Spark 根据 RDD 的血统(Lineage)构建 DAG。

python 复制代码
rdd1 = sc.textFile("data.txt")          # 初始 RDD
rdd2 = rdd1.map(lambda x: x.upper())    # 窄依赖
rdd3 = rdd2.filter(lambda x: "ERROR" in x)  # 窄依赖
rdd4 = rdd3.groupByKey()               # 宽依赖
rdd4.collect()                         # 触发 DAG 构建

对应的 DAG:
map filter groupByKey rdd1 rdd2 rdd3 rdd4

1.3.4.2 Stage

定义:DAG 的物理执行单元,分为 ShuffleMapStage(Shuffle 数据准备)和 ResultStage(最终计算)。

划分规则:

窄依赖操作合并为同一 Stage。

宽依赖触发 Stage 分割。
Shuffle map, filter reduceByKey

划分 Stage 的核心作用

优化执行效率:

窄依赖操作合并为流水线(Pipeline):同一 Stage 内的连续窄依赖操作(如 map→filter)合并为单个 Task 执行,避免中间数据落盘。

宽依赖强制划分 Stage:确保 Shuffle 前所有数据准备完成,明确任务执行边界。

容错管理:

Stage 是容错的最小单元,失败时只需重算该 Stage 及其后续 Stage。

Stage 划分规则:

以宽依赖为边界,将 DAG 切分为多个 Stage。

窄依赖操作合并到同一 Stage。
Shuffle Stage 1: map + filter Stage 2: groupByKey

map filter 是窄依赖,合并为 Stage 1。
groupByKey 是宽依赖,触发 Stage 2。

Shuffle 发生的环节

在 Spark 中,Shuffle 是连接不同 Stage 的桥梁,通常发生在以下操作中:

宽依赖操作:如 groupByKeyreduceByKey等需要跨分区重新分配数据的操作。

当触发 Shuffle 时,Spark 会将当前 Stage 的 Task 划分为 Shuffle Write(数据写出)和 Shuffle Read(数据读取)两个阶段,分属不同 Stage 的上下游 Task 3。

1.3.5 Driver 程序

Driver 是用户编写的应用主逻辑:

用户必须自行编写 Driver 程序(如 Python/Scala/Java 代码),在其中定义数据处理流程(创建 RDD/DataFrame,调用转换和行动操作)。

python 复制代码
# 用户编写的 Driver 程序(必须存在)
from pyspark import SparkContext
sc = SparkContext("local", "WordCount")
rdd = sc.textFile("hdfs://input.txt")
result = rdd.flatMap(lambda line: line.split(" ")) \
            .countByValue()  # Action 操作
print(result)

1.4 Spark计算流程

Executor Task1 Task2 Task3 Executor 内存计算 Stage 1: 窄依赖任务 Pipeline 执行 Executor 内存计算 Executor 内存计算 Stage 2: 下游任务执行 Driver 程序 创建初始 RDD 定义转换操作链 map/filter/join 触发 Action 操作 collect/save DAGScheduler 将 DAG 划分为 Stage Shuffle 阶段: 宽依赖数据重组 结果返回 Driver 或写入存储

Spark与MapReduce对比

对比维度 Spark MapReduce 优劣分析
执行模型 基于内存的DAG模型,多阶段流水线执行 严格的Map-Shuffle-Reduce两阶段模型 Spark减少中间数据落盘,MR依赖磁盘保证稳定性
容错机制 基于RDD血缘关系(Lineage)重建丢失分区 通过Task重试和HDFS副本机制 Spark重建成本低,MR依赖数据冗余保障可靠性
延迟性能 亚秒级到秒级(内存计算) 分钟级(多轮磁盘IO) Spark适合交互式查询/实时处理,MR仅适合离线批处理
适用场景 迭代计算(ML)、实时流处理、交互式分析 超大规模离线批处理(TB/PB级) Spark覆盖场景更广,MR在单一超大Job场景仍有优势

2. SparkSQL

Spark SQL 是 Apache Spark 的模块之一,核心功能是 处理结构化数据,其设计目标是:统一 SQL 与代码:允许开发者混合使用 SQL 和 DataFrame API(Python/Scala/Java),打破 SQL 与编程语言的界限。

高性能查询:通过 Catalyst 优化器生成逻辑/物理执行计划,结合 Tungsten 引擎的二进制内存管理,提升执行效率。

多数据源支持:可对接 Hive、JSON、Parquet、JDBC 等数据源,甚至自定义数据源。

Spark Sql转换成程序执行的过程
RDD与DAG 物理执行 Catalyst优化器 转换为RDD操作链 基于宽依赖划分Stage 生成DAG DAGScheduler调度Stage Task分发到Executor 生成物理计划 Tungsten引擎代码生成 生成未解析逻辑计划 SQL 解析 元数据绑定 解析后的逻辑计划 逻辑优化 优化后的逻辑计划 输入 SQL 或 DataFrame 操作 集群执行并返回结果

3. Spark Streaming

3.1 核心定位

Spark Streaming 是 Spark 生态中用于实时流数据处理的模块,采用 微批处理(Micro-Batch) 模式,将流数据切分为小批次(如1秒~数分钟),以类似批处理的方式实现近实时(Near-Real-Time)计算。其核心优势是与 Spark 生态的无缝集成,复用批处理代码和资源调度能力。

3.2 离散化流DStream与结构化流Structured Streaming

数据抽象:DStream

离散化流(Discretized Stream, DStream):是 Spark Streaming 的基础抽象,表示一个连续的数据流,内部由一系列 RDD 组成,每个 RDD 对应一个时间窗口内的数据。

处理流程:

输入源:从 Kafka、Flume、Kinesis、TCP Socket 等读取数据。

批处理窗口:按用户定义的批次间隔(如5秒)将数据划分为微批次。

转换操作:对每个批次的 RDD 应用 map、reduce、join 等操作。

输出结果:将处理后的数据写入 HDFS、数据库或实时看板。

执行模型

Driver 节点:负责调度任务,生成 DStream 的 DAG 执行计划。

Worker 节点:执行具体的任务(Task),处理每个微批的 RDD。

容错机制:基于 RDD 的血缘(Lineage)机制,自动恢复丢失的分区数据。

Structured Streaming(结构化流)

核心抽象:将流数据视为无界的 DataFrame/Dataset,支持与静态数据相同的 SQL 操作。

处理模式:

微批处理(默认):类似传统 Spark Streaming。

连续处理(实验性):实现毫秒级延迟(Spark 2.3+)。

关键特性:

端到端 Exactly-Once 语义:通过与 Kafka 等源的协同,保证数据不丢不重。

事件时间与水位线:支持基于事件时间的窗口聚合,处理乱序数据。

状态管理:内置状态存储(StateStore),支持 mapGroupsWithState 等复杂状态操作。

相关推荐
成长之路5141 小时前
【工具变量】最新华证ESG评级得分数据-含xlsx及dta格式(2009-2024.12)
大数据
sszmvb12341 小时前
unittest自动化测试实战
软件测试·面试
巴拉特好队友2 小时前
说说es配置项的动态静态之分和集群配置更新API
大数据·elasticsearch·搜索引擎
End9282 小时前
MapReduce中的分区器
大数据·hadoop
小Tomkk2 小时前
怎么在非 hadoop 用户下启动 hadoop
大数据·hadoop·问题
大学生小郑2 小时前
Go语言八股之并发详解
面试·golang
极小狐3 小时前
极狐GitLab 如何将项目共享给群组?
大数据·数据库·elasticsearch·机器学习·gitlab
半桔3 小时前
定长滑动窗口---初阶篇
数据结构·c++·算法·leetcode·面试
结冰架构4 小时前
【AI提示词】AARRR 模型执行者
大数据·人工智能·ai·提示词·思维模型
SunTecTec4 小时前
SQL Server To Paimon Demo by Flink standalone cluster mode
java·大数据·flink