第7期:Spark内存计算引擎 - 弹性分布式数据集的数学原理与工业优化
导言:任何不理解Spark RDD设计哲学的工程师无法胜任大数据平台的性能优化。本期我们将深入Spark的核心设计,从RDD的不可变性论出发,阐明Lineage血统追踪的容错原理;解析DAG调度器的优化策略;以及DataFrame/Dataset如何通过Tungsten引擎实现工业级性能提升。
7.1 RDD的数学本质
7.1.1 不可变性的数学定义
RDD(Resilient Distributed Dataset)的核心设计哲学源于函数式编程的不可变性:
RDD的数学形式化:
定义1:RDD是不可变的分布式数据集
RDD[T] = ⟨
partitions: Set[Partition], -- 分区集合
compute: Partition → Iterator[T], -- 计算函数
dependencies: List[Dependency], -- 依赖关系
partitioner: Option[Partitioner], -- 分区器
preferredLocations: Partition → Set[Location] -- 位置信息
⟩
定义2:RDD的转换操作是纯函数
transform(rdd, f) = new_rdd
其中 new_rdd.compute(p) = f(rdd.compute(p))
定义3:Lineage血统追踪
设转换序列为 T = [t₁, t₂, ..., tₙ]
则最终RDD可以通过回溯T重建,无需数据复制
Lineage容错数学证明:
- 设RDD_n因节点故障丢失分区p
- 回溯Lineage找到 tₖ: parent(RDD_n) = RDD_{n-1}
- 重新计算 RDD_n.partitions[p] = tₖ(rdd_{n-1}.partitions[p])
结论:RDD的容错不需要检查点,仅需重新计算
#mermaid-svg-Bsv3v9xPu0oDzNwL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bsv3v9xPu0oDzNwL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bsv3v9xPu0oDzNwL .error-icon{fill:#552222;}#mermaid-svg-Bsv3v9xPu0oDzNwL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bsv3v9xPu0oDzNwL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bsv3v9xPu0oDzNwL .marker.cross{stroke:#333333;}#mermaid-svg-Bsv3v9xPu0oDzNwL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bsv3v9xPu0oDzNwL p{margin:0;}#mermaid-svg-Bsv3v9xPu0oDzNwL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bsv3v9xPu0oDzNwL .cluster-label text{fill:#333;}#mermaid-svg-Bsv3v9xPu0oDzNwL .cluster-label span{color:#333;}#mermaid-svg-Bsv3v9xPu0oDzNwL .cluster-label span p{background-color:transparent;}#mermaid-svg-Bsv3v9xPu0oDzNwL .label text,#mermaid-svg-Bsv3v9xPu0oDzNwL span{fill:#333;color:#333;}#mermaid-svg-Bsv3v9xPu0oDzNwL .node rect,#mermaid-svg-Bsv3v9xPu0oDzNwL .node circle,#mermaid-svg-Bsv3v9xPu0oDzNwL .node ellipse,#mermaid-svg-Bsv3v9xPu0oDzNwL .node polygon,#mermaid-svg-Bsv3v9xPu0oDzNwL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bsv3v9xPu0oDzNwL .rough-node .label text,#mermaid-svg-Bsv3v9xPu0oDzNwL .node .label text,#mermaid-svg-Bsv3v9xPu0oDzNwL .image-shape .label,#mermaid-svg-Bsv3v9xPu0oDzNwL .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bsv3v9xPu0oDzNwL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bsv3v9xPu0oDzNwL .rough-node .label,#mermaid-svg-Bsv3v9xPu0oDzNwL .node .label,#mermaid-svg-Bsv3v9xPu0oDzNwL .image-shape .label,#mermaid-svg-Bsv3v9xPu0oDzNwL .icon-shape .label{text-align:center;}#mermaid-svg-Bsv3v9xPu0oDzNwL .node.clickable{cursor:pointer;}#mermaid-svg-Bsv3v9xPu0oDzNwL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bsv3v9xPu0oDzNwL .arrowheadPath{fill:#333333;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bsv3v9xPu0oDzNwL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bsv3v9xPu0oDzNwL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bsv3v9xPu0oDzNwL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bsv3v9xPu0oDzNwL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bsv3v9xPu0oDzNwL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bsv3v9xPu0oDzNwL .cluster text{fill:#333;}#mermaid-svg-Bsv3v9xPu0oDzNwL .cluster span{color:#333;}#mermaid-svg-Bsv3v9xPu0oDzNwL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bsv3v9xPu0oDzNwL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bsv3v9xPu0oDzNwL rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bsv3v9xPu0oDzNwL .icon-shape,#mermaid-svg-Bsv3v9xPu0oDzNwL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bsv3v9xPu0oDzNwL .icon-shape p,#mermaid-svg-Bsv3v9xPu0oDzNwL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bsv3v9xPu0oDzNwL .icon-shape .label rect,#mermaid-svg-Bsv3v9xPu0oDzNwL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bsv3v9xPu0oDzNwL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bsv3v9xPu0oDzNwL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bsv3v9xPu0oDzNwL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Lineage血统
Action (触发执行)
Transformation (惰性)
RDD创建
SparkContext
并行集合
parallelize()
外部存储
textFile()
HDFS/HBase
数据源
map(f)
filter(f)
groupByKey()
join()
collect()
count()
saveAsTextFile()
DAG构建
Stage划分
Task调度
7.1.2 RDD依赖类型的数学分类
python
"""
Spark RDD依赖类型的形式化定义
"""
from abc import ABC, abstractmethod
from typing import List, Iterator, Set, TypeVar
T = TypeVar('T')
K = TypeVar('K')
V = TypeVar('V')
class Dependency(ABC):
"""依赖抽象基类"""
@abstractmethod
def parent_partitions(self, partition_id: int) -> List[int]:
"""获取父RDD的分区"""
pass
class NarrowDependency(Dependency):
"""
窄依赖:每个父分区最多被一个子分区使用
数学定义:
∀p_child ∈ child.partitions:
|{p_parent ∈ parent.partitions | maps(p_child, p_parent)}| ≤ 1
特点:一个分区只依赖父RDD的最多一个分区
可以管道化执行,无需Shuffle
"""
def parent_partitions(self, partition_id: int) -> List[int]:
raise NotImplementedError
class WideDependency(Dependency):
"""
宽依赖:每个父分区可能被多个子分区使用
数学定义:
∃p_child ∈ child.partitions:
|{p_parent ∈ parent.partitions | maps(p_child, p_parent)}| > 1
特点:需要Shuffle,所有父分区必须可用
必须等待所有父分区数据就绪
"""
class OneToOneDependency(NarrowDependency):
"""一对一依赖:父子分区一一对应"""
def parent_partitions(self, partition_id: int) -> List[int]:
return [partition_id] # 直接返回相同分区
class RangeDependency(NarrowDependency):
"""范围依赖:有序分区映射"""
def parent_partitions(self, partition_id: int) -> List[int]:
out_offset = partition_id - self.out_start
return [self.in_start + out_offset]
class ShuffleDependency(WideDependency):
"""Shuffle依赖:需要重新分区"""
def parent_partitions(self, partition_id: int) -> List[int]:
# 反向计算:根据Reducer ID获取Mapper ID集合
shuffleId = self.shuffleId
reduceId = partition_id
# 获取该Reducer对应的所有Mapper分区
mapperPartitions = self.shuffleManager.getMapPartitions(
shuffleId, reduceId
)
return mapperPartitions
# RDD实现
class RDD(ABC):
"""RDD抽象基类"""
def __init__(self, ctx, deps: List[Dependency]):
self.ctx = ctx
self._dependencies = deps
self._partitions = None
self._partitioner = None
@abstractmethod
def compute(self, split: 'Partition', context: 'TaskContext') -> Iterator[T]:
"""计算单个分区"""
pass
@property
def partitions(self) -> List['Partition']:
"""获取所有分区"""
if self._partitions is None:
self._partitions = self.get_partitions()
return self._partitions
def get_partitions(self) -> List['Partition']:
"""子类实现:返回分区列表"""
raise NotImplementedError
def get_preferred_locations(self, split: 'Partition') -> List[str]:
"""获取分区位置信息"""
return [] # 默认返回空
def __str__(self) -> str:
return f"RDD[{self.__class__.__name__}]"
7.2 DAG调度器的数学原理
7.2.1 Stage划分的数学算法
Spark的DAG调度器将执行计划划分为Stage:
Stage划分的数学算法:
输入:DAG = (RDDs, Edges) 其中 Edges 表示RDD间的依赖关系
输出:Stages = [Stage₁, Stage₂, ..., Stageₘ]
算法:
1. 构建逆向依赖图(从Action出发)
2. 从最后一个RDD向前遍历
3. 遇到宽依赖(ShuffleDependency)则划分新的Stage
4. 每个Stage包含连续的窄依赖RDD
形式化描述:
Stage_s = {rdd |
rdd ∈ DAG ∧
exists Path(rdd, lastRDD) ∧
∀edge(u→v) on Path: edge.type = NarrowDependency
}
Stage划分示例:
RDD_A → map → RDD_B → filter → RDD_C → shuffle → RDD_D → reduce → RDD_E
划分结果:
- Stage_0: [RDD_A, RDD_B, RDD_C] (窄依赖链)
- Stage_1: [RDD_D, RDD_E] (从Shuffle后开始)
7.2.2 DAG调度器工业级实现
java
/**
* Spark DAG调度器核心实现
*/
public class DAGScheduler {
private final SparkContext sc;
private final TaskScheduler taskScheduler;
private final Map<StageId, Stage> stages;
private final Map<JobId, ActiveJob> activeJobs;
/**
* 提交Job,生成执行计划
*/
def submitJob(rdd: RDD[_], func: (TaskContext, Iterator[_]) => _):
// 1. 生成finalRDD的ShuffleDep
val shuffleDep = getShuffleDep(finalRDD)
// 2. 从finalRDD回溯构建Stage
val stages = getOrCreateShuffleMapStages(shuffleDep)
// 3. 添加finalStage(ResultStage)
val finalStage = getOrCreateResultStage(finalRDD, partitions)
stages += finalStage
// 4. 提交所有Stage
submitStage(finalStage)
/**
* Stage划分核心算法
*/
private def getOrCreateShuffleMapStages(shuffleDep: ShuffleDependency):
val shuffleId = shuffleDep.shuffleId
// 检查是否已存在
if (mapStageJobs.contains(shuffleId)) {
return mapStageJobs(shuffleId)
}
// 递归创建父Stage
val parentStages = getParentStages(shuffleDep.rdd)
// 创建当前Stage
val stage = new ShuffleMapStage(
id = newStageId(),
rdd = shuffleDep.rdd,
numPartitions = shuffleDep.rdd.partitions.length,
parentStages = parentStages,
shuffleDep = shuffleDep
)
// 缓存并返回
mapStageJobs(shuffleId) = stage
stages(stage.id) = stage
return stage
/**
* 从RDD向前回溯创建Stage
*/
private def getParentStages(rdd: RDD[_]): List[Stage] = {
val parents = new ListBuffer[Stage]
val visited = new HashSet[RDD[_]]
def visit(rdd: RDD[_]): Unit = {
if (visited.contains(rdd)) return
visited += rdd
// 获取依赖
for (dep <- rdd.dependencies) {
dep match {
case shuffleDep: ShuffleDependency[_, _] =>
// 遇到Shuffle依赖,创建新的Stage
parents += getOrCreateShuffleMapStages(shuffleDep)
case narrowDep: NarrowDependency[_] =>
// 窄依赖继续向前遍历
visit(narrowDep.rdd)
}
}
}
visit(rdd)
return parents.toList
}
}
7.3 Tungsten引擎:内存优化的数学突破
7.3.1 Unsafe Row:紧凑二进制格式
python
"""
Spark Tungsten Unsafe Row格式
"""
class UnsafeRow:
"""
Tungsten的Unsafe Row是一种紧凑的二进制格式
特点:
- 固定宽度的列存储在固定位置
- 可变宽度列存储指针
- 允许直接内存访问,避免对象开销
内存布局:
┌─────────────────────────────────────────────────────┐
│ null bits (1 bit per column) | col_0 | col_1 | ... │
│ ↑ 8字节对齐 │
└─────────────────────────────────────────────────────┘
数学分析:
- 对象开销节省:Java对象头 ~12-16 bytes
- 缓存行利用:Unsafe Row 128 bytes = 2个缓存行
- GC压力降低:无需频繁创建对象
"""
# 列类型到偏移量的计算
FIXED_SIZE_TYPES = {
'int': 4,
'long': 8,
'double': 8,
'float': 4,
'boolean': 1,
}
def __init__(self, num_fields: int):
# null bits需要8字节对齐
self.num_bits = num_fields
self.null_bits_size = ((num_fields + 63) // 64) * 8
# 计算每列的偏移量
self.offsets = []
offset = self.null_bits_size
for i in range(num_fields):
self.offsets.append(offset)
# 这里简化了计算,实际还需要考虑对齐
offset += 8 # 假设都是8字节
# 分配内存
self.total_size = offset
self.buffer = bytearray(self.total_size)
def set_int(self, ordinal: int, value: int):
"""设置int列"""
base_offset = self.offsets[ordinal]
# 使用内存拷贝而非对象创建
import struct
struct.pack_into('<i', self.buffer, base_offset, value)
self.clear_null_at(ordinal)
def get_int(self, ordinal: int) -> int:
"""获取int列"""
base_offset = self.offsets[ordinal]
return struct.unpack_from('<i', self.buffer, base_offset)[0]
def is_null_at(self, ordinal: int) -> bool:
"""检查null"""
word = struct.unpack_from('<Q',
self.buffer, (ordinal // 64) * 8)[0]
return (word >> (ordinal % 64)) & 1 == 1
def clear_null_at(self, ordinal: int):
"""清除null标记"""
word_offset = (ordinal // 64) * 8
mask = ~(1 << (ordinal % 64))
word = struct.unpack_from('<Q', self.buffer, word_offset)[0]
struct.pack_into('<Q', self.buffer, word_offset, word & mask)
7.3.2 Spark内存管理模型
Spark统一内存模型:
┌─────────────────────────────────────────────────────────────┐
│ Execution Memory │
│ ────────────────────────────────────────────────────────│
│ 用于Shuffle、Join、Sort等计算的内存 │
│ 可以借用Storage Memory (当Execution不足时) │
│ 但不能反向借用 │
├─────────────────────────────────────────────────────────────┤
│ Storage Memory │
│ ────────────────────────────────────────────────────────│
│ 用于缓存RDD、数据广播等 │
│ 可以借用Execution Memory (当Storage不足时) │
│ 但不能反向借用 │
├─────────────────────────────────────────────────────────────┤
│ User Memory │
│ ────────────────────────────────────────────────────────│
│ 用户数据结构、元数据等 │
│ 不与Execution/Storage共享 │
├─────────────────────────────────────────────────────────────┤
│ Reserved Memory │
│ ────────────────────────────────────────────────────────│
│ 系统预留: 300MB │
└─────────────────────────────────────────────────────────────┘
公式:
totalMemory = systemMemory × spark.memory.fraction (默认0.6)
executionMemory = totalMemory × spark.memory.storageFraction (默认0.5)
storageMemory = totalMemory × (1 - spark.memory.storageFraction)
7.4 工业场景Spark优化实战
7.4.1 数据倾斜解决方案
python
"""
Spark数据倾斜解决方案
"""
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, lit, when, hash, rand
from pyspark.sql.types import StringType
class DataSkewOptimizer:
"""数据倾斜优化器"""
def __init__(self, spark: SparkSession):
self.spark = spark
def solution_1_salt_broadcast(self, df, skewed_col, join_df):
"""
方案1:加盐 + 广播小表
适用于:Join时大表倾斜,小表不倾斜
"""
# 识别倾斜Key
skewed_keys = df.groupBy(skewed_col) \
.count() \
.filter("count > 10000") \
.select(skewed_col) \
.collect()
skewed_key_set = set([row[skewed_col] for row in skewed_keys])
# 对倾斜Key加盐
NUM_SALT = 100
df_salted = df.withColumn(
"salt",
when(col(skewed_col).isin(skewed_key_set),
(rand() * NUM_SALT).cast("int"))
.otherwise(lit(0))
).withColumn(
skewed_col,
when(col(skewed_col).isin(skewed_key_set),
col(skewed_col).cast(StringType) + "_" + col("salt").cast(StringType))
.otherwise(col(skewed_col))
)
# 广播小表并添加盐列
join_df_broadcast = join_df.crossJoin(
self.spark.range(NUM_SALT).withColumnRenamed("id", "salt")
).withColumn(
skewed_col,
col(skewed_col).cast(StringType) + "_" + col("salt").cast(StringType)
).drop("salt")
# 执行Join
result = df_salted.join(join_df_broadcast, skewed_col, "left")
return result
def solution_2_differentail_join(self, df1, df2, join_key):
"""
方案2:双重Join(打散热点 + 聚合非热点)
适用于:聚合操作倾斜
"""
# 分离热点和非热点
HOT_THRESHOLD = 10000
skewed_keys = df1.groupBy(join_key) \
.count() \
.filter(f"count > {HOT_THRESHOLD}") \
.select(join_key)
# 打散热点数据
df1_hot = df1.join(skewed_keys, join_key) \
.withColumn("skew_key", concat(
col(join_key),
lit("_"),
(rand() * 9).cast("int")
))
# 广播小表处理热点
skewed_keys_broadcast = skewed_keys.collect()
df2_hot = df2.filter(col(join_key).isin(
[row[join_key] for row in skewed_keys_broadcast]
))
result_hot = df1_hot.join(df2_hot,
[join_key, "skew_key"], "left")
# 非热点普通Join
df1_cold = df1.join(skewed_keys, join_key, "left_anti")
df2_cold = df2.join(skewed_keys, join_key, "left_anti")
result_cold = df1_cold.join(df2_cold, join_key, "left")
return result_hot.union(result_cold)
7.4.2 工业级Spark配置
python
"""
Spark工业级配置推荐
"""
SPARK_INDUSTRIAL_CONFIGS = {
# ===== 内存配置 =====
"spark.executor.memory": "16g",
"spark.executor.memoryOverhead": "4g", # 堆外内存
"spark.driver.memory": "8g",
"spark.memory.fraction": "0.85", # Execution+Storage占85%
"spark.memory.storageFraction": "0.5",
# ===== CPU配置 =====
"spark.executor.cores": "4", # 每个Executor 4核
"spark.task.cpus": "2", # 每个Task 2核(I/O密集)
"spark.default.parallelism": "800", # 默认并行度
# ===== Shuffle配置 =====
"spark.shuffle.service.enabled": "true",
"spark.shuffle.sort.bypassMergeThreshold": "200", # 小数据量绕过Sort
"spark.sql.shuffle.partitions": "800", # Shuffle分区数
# ===== 网络配置 =====
"spark.network.timeout": "300s",
"spark.executor.heartbeatInterval": "30s",
"spark.rpc.message.maxSize": "256",
# ===== Kryo序列化 =====
"spark.serializer": "org.apache.spark.serializer.KryoSerializer",
"spark.kryo.registrationRequired": "false",
"spark.kryo.classesToRegister": "java.util.ArrayList;java.util.HashSet",
# ===== Tungsten优化 =====
"spark.sql tungsten.enabled": "true",
"spark.sql.codegen": "true",
"spark.sql.inMemoryColumnarStorage.compressed": "true",
}
def get_optimized_spark_config(worker_type: str) -> dict:
"""根据工作负载类型获取优化配置"""
configs = {
"ml_training": {
**SPARK_INDUSTRIAL_CONFIGS,
"spark.executor.memory": "32g",
"spark.executor.cores": "8",
"spark.memory.fraction": "0.9",
},
"streaming": {
**SPARK_INDUSTRIAL_CONFIGS,
"spark.streaming.backpressure.enabled": "true",
"spark.streaming.kafka.maxRatePerPartition": "10000",
},
"etl": {
**SPARK_INDUSTRIAL_CONFIGS,
"spark.sql.shuffle.partitions": "1600",
"spark.sql.adaptive.enabled": "true",
}
}
return configs.get(worker_type, SPARK_INDUSTRIAL_CONFIGS)
7.5 本期小结
┌─────────────────────────────────────────────────────────────┐
│ Spark内存计算知识体系 │
├─────────────────────────────────────────────────────────────┤
│ 第1层:RDD理论层 │
│ ├── RDD定义:不可变、分布式、分区数据集 │
│ ├── Lineage追踪:容错无需检查点 │
│ ├── 依赖分类:窄依赖(管道化) vs 宽依赖(Shuffle) │
│ └── 惰性求值:Transformation不执行,Action触发 │
├─────────────────────────────────────────────────────────────┤
│ 第2层:DAG调度层 │
│ ├── Stage划分:从后向前,遇到Shuffle则分段 │
│ ├── Pipeline执行:同一Stage内管道化 │
│ ├── 任务调度:Stage内并行Task │
│ └── 本地性调度:Process > Node > Rack │
├─────────────────────────────────────────────────────────────┤
│ 第3层:Tungsten优化层 │
│ ├── Unsafe Row:紧凑二进制格式,节省内存 │
│ ├── Cache-aware计算:利用CPU缓存优化 │
│ ├── 内存模型:Execution/Storage可借用 │
│ └── Off-heap存储:绕过JVM堆限制 │
├─────────────────────────────────────────────────────────────┤
│ 第4层:工业优化层 │
│ ├── 数据倾斜:加盐、广播、聚合分离 │
│ ├── Shuffle优化:外部Shuffle服务、Sort Merge │
│ ├── 广播优化:小表广播,避免Shuffle │
│ └── 自适应执行:AQE自动调整分区数 │
└─────────────────────────────────────────────────────────────┘
下期预告 :第8期:Flink流处理引擎 - 事件时间的工业级处理语义------深入解析Flink的WaterMark机制、窗口计算、以及Exactly-Once的端到端保证。
作者:高炉炼铁智能化技术研究者,专注钢铁冶金与人工智能 交叉领域。
👍 如果觉得有帮助,请点赞、收藏、转发!
版权归作者所有,未经许可请勿抄袭,套用,商用(或其它具有利益性行为)。
🔔 关注专栏,不错过后续精彩内容!