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(惰性执行,不立即计算)。比如map
、filter
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):触发实际计算并返回结果或持久化数据(立即执行)。比如collect
、count
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 的桥梁,通常发生在以下操作中:
宽依赖操作:如 groupByKey
、reduceByKey
等需要跨分区重新分配数据的操作。
当触发 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 等复杂状态操作。