当前位置: 首页 > news >正文

大数据面试问答-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

# 转换算子链(无实际计算)
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

# 触发计算,返回结果
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) 形式的键值对。

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

groupByKey 的 SQL 对应形式
Spark RDD 代码:

rdd.groupByKey().mapValues(list)  # 输出 (Key, [Value1, Value2, ...])

Spark SQL 代码:

SELECT key, COLLECT_LIST(value) AS values
FROM table
GROUP BY key;

reduceByKey 的 SQL 对应形式
Spark RDD 代码:

rdd.reduceByKey(lambda a, b: a + b)  # 输出 (Key, Sum_Value)

Spark 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。

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,调用转换和行动操作)。

# 用户编写的 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对比

对比维度SparkMapReduce优劣分析
执行模型基于内存的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 等复杂状态操作。


http://www.mrgr.cn/news/98377.html

相关文章:

  • 嵌入式程序设计英语
  • Spring Security6 从源码慢速开始
  • HarmonyOS:使用Refresh组件实现页面下拉刷新上拉加载更多
  • PVE 8.4.1 安装 KDE Plasma 桌面环境 和 PVE换源
  • linux中查看.ypc二进制文件
  • Linux服务之网络共享
  • Melos 发布pub.dev
  • 30学Java第十天——类加载的过程
  • 【动手学强化学习】番外7-MAPPO应用框架2学习与复现
  • AWS Redshift的使用场景及一些常见问题
  • 绿算轻舟系列FPGA加速卡:驱动数字化转型的核心动力
  • electron-builder参数详解
  • ukui-greeter编译与安装
  • C/C++的数据类型
  • 数据库原理及应用mysql版陈业斌实验三
  • mongodb 安装配置
  • AI 项目详细开发步骤指南
  • antv x6使用(支持节点排序、新增节点、编辑节点、删除节点、选中节点)
  • 【Java集合】HashMap源码深度分析
  • 大数据面试问答-批处理性能优化