📅 训练日期:2026-04-18
📊 今日正确率:42%(题目难度升级,概念框架已建立,细节待打磨)
🎯 核心收获:从"孤立名词"升级为"互相关联的认知网"
🗺️ 大厂数据链路全景图(一切的起点)
把今天所有概念放在一张图里,这是今后看任何数据开发题的思维地图:
💡 类比:SQL 是订单,Catalyst 是菜谱设计师,RDD 是菜谱本身,Spark 是厨师,Hive Metastore 是菜单管理员,YARN 是调度员,HDFS 是冰箱。
🔗 各组件之间的精确关系
关系 | 含义 |
Catalyst → RDD | Catalyst 的最终产出就是 RDD DAG |
Catalyst → Hive Metastore | Catalyst 在 Analyze 阶段去 Metastore 查字段类型 |
Hive Metastore ↔ Hive SQL 引擎 | 两者可分开使用;Spark 只借用 Metastore |
RDD ↔ DataFrame | DataFrame 底层还是 RDD,只是多了 Schema |
Spark ↔ Hadoop | Spark 借用 Hadoop 的 HDFS + YARN,但用自己的计算引擎替代 MapReduce |
DAG ↔ Stage ↔ Task | DAG 按宽依赖切 Stage,一个 Stage 包含多个并行 Task |
1️⃣ 四大天王:Hadoop / MapReduce / Hive / Spark
🧩 Hadoop:大数据基础设施"全家桶"
Hadoop 不是一个软件,是一套系统。
组件 | 职责 | 类比 |
HDFS | 分布式存储 | 超大号冰箱,分布在多个仓库 |
YARN | 资源调度 | 厨房调度员 |
MapReduce | 计算引擎 | 老式厨师 |
🎯 关键认知:别人说"用 Hadoop",99% 是指 HDFS + YARN + 某种计算引擎(可能是 MR 也可能是 Spark)。
🐢 MapReduce:最老的计算引擎(被淘汰中)
核心流程:Map → Shuffle → Reduce
三大痛点:
- 中间结果必须写磁盘(I/O 重)
- 迭代算法灾难(机器学习跑不动)
- API 难写(WordCount 需要 200 行 Java)
现状: 新项目基本不用了,但笔试超高频(原理题的经典出处)。
🐘 Hive:SQL 翻译器 + 元数据管理
Hive 其实包含两个可独立使用的模块:
模块 | 职责 | 现状 |
Hive SQL 引擎 | 把 SQL 翻译成 MR/Tez/Spark 作业 | 越来越边缘化 |
Hive Metastore | 管元数据(表、字段、分区、HDFS 路径) | 大厂事实标准 ⭐ |
🎯 关键认知:Hive 不存数据!数据存在 HDFS,Hive 只是"帮你管表" + "翻译 SQL"。
⚡ Spark:新一代计算引擎(主流)
Spark 比 MapReduce 快在哪?(必背 4 点)
- 内存计算:中间结果优先内存
- DAG 调度:整体优化,不是死板的两阶段
- 算子丰富:80+ 算子,MR 只有 map/reduce
- 线程级并行:Task 是线程,MR 是进程
⚠️ 陷阱:别说"Spark 纯内存"——内存不够照样落盘,Shuffle 一定落盘。
Spark 生态包含:
- Spark Core(RDD)
- Spark SQL(结构化数据)
- Spark Streaming(流处理)
- MLlib(机器学习)
- GraphX(图计算)
⚠️ Spark on Hive vs Hive on Spark(超高频陷阱题)
名称 | 主角 | SQL 引擎 | 元数据 | 执行 |
Spark on Hive ⭐大厂主流 | Spark | Spark Catalyst | Hive Metastore | Spark |
Hive on Spark | Hive | Hive SQL 引擎 | Hive Metastore | Spark |
🎯 一句话记忆:Spark 瞧不上 Hive 的 SQL 引擎(自己的 Catalyst 更强),但离不开 Hive 的 Metastore(元数据事实标准)。
2️⃣ Spark 不是数仓!厘清概念边界
📚 Spark / 数仓 / SQL / 存储 的精确定位
层次 | 代表 | 职责 |
存储层 | HDFS / S3 | 存数据 |
数仓层 | Hive(分层建模 ODS/DWD/DWS/ADS) | 组织数据的方法论 |
计算引擎 | Spark / MR / Flink | 算数据 |
查询语言 | SQL | 表达需求 |
🎯 关键认知:
- Spark ≠ 数仓。它只管算,不管存,也不管建模。
- 数仓是方法论,具体产品是 Hive、MaxCompute 等。
- SQL 是语言不是产品,同一段 SQL 在不同引擎里执行路径完全不同。
🔤 SQL 的本质:声明式语言
声明式:你只说"要什么",不说"怎么做"。
你没说:
- 要不要用内存
- 要不要 Shuffle
- 怎么分区
→ "怎么做"是执行引擎(Spark/Hive/MySQL)的事
🎯 同一段 SQL 在不同引擎里被翻译成不同的物理计划:
- MySQL → 走 B+ 树索引
- Hive → 翻译成 MR 作业
- Spark → Catalyst 翻译成 RDD DAG
- Flink → 翻译成流处理算子
3️⃣ Spark 核心概念(今日主菜)
3.1 RDD:Spark 的基础抽象
🧠 RDD 的本质:菜谱,不是菜
RDD = Resilient Distributed Dataset
- Resilient(弹性):分区丢了能自动重算
- Distributed(分布式):切成分区散在多台机器
- Dataset(数据集)—— ⚠️ 这个词是翻译陷阱!
🎯 RDD 的真实身份:不是数据集,是"如何得到数据集的计算描述"(菜谱,不是菜)。
关键认知 3 点:
- RDD 是分布式集合的抽象,里面能装任意对象(数字、字符串、字典、自定义类)
- 默认不持有数据,只是计算图;Action 触发执行,cache 触发物化
- 核心价值:让数据"分散并行处理",不是"合并处理"
- ❌ 错:RDD 把数据合并起来算
- ✅ 对:RDD 让 100 TB 数据留在 1000 台机器上各自算,必要时才汇总(Shuffle)
🏷️ RDD 五大特性(必背)
# | 特性 | 说明 |
1 | 分区列表 Partitions | 数据切成多个分区 |
2 | 计算函数 Compute | 每个分区有自己的计算函数 |
3 | 依赖关系 Dependencies | RDD 之间有血缘(lineage) |
4 | 分区器 Partitioner | KV RDD 才有(Hash / Range) |
5 | 优先位置 Preferred Locations | 数据本地性 |
口诀:分区、计算、依赖、分区器、位置
🎯 容错机制:基于 lineage 重算,不需要存多副本。
🏪 RDD 的类比:跨国连锁超市
你是总部,全球 1000 家门店,每家 10 万件库存。
- 每家门店 = 一个分区(Partition)
- 店员数自己店的货 = 窄依赖操作(map/filter)
- 门店汇总数上报总部 = Shuffle(宽依赖)
- 整个库存系统 = 一个 RDD
🎯 核心思想:RDD 不是把数据合并到一起算,而是让数据散着算——这是它能处理 PB 级数据的根本原因。
3.2 Schema:一切优化的前提
📋 Schema 是什么,为什么关键
Schema = 对数据结构的描述(字段名 + 类型)
ㅤ | 无 Schema | 有 Schema |
数据样子 | 一堆字节/对象 | 带表头的表格 |
访问字段 | x[2](靠下标) | df.age(靠字段名) |
类型处理 | 手动 int() | 自动 |
Spark 能否优化 | ❌ 不能 | ✅ 能(Catalyst) |
🎯 一句话定位:Schema 是 Spark 能做所有性能优化的前提。
3.3 RDD / DataFrame / Dataset:层层包装关系
🎂 三者不是替代关系,是三层蛋糕
对比表(必背):
维度 | RDD | DataFrame | Dataset |
Schema | 无 | 有 | 有 |
类型检查 | 运行时 | 运行时 | 编译时 |
优化器 | 无 | Catalyst | Catalyst |
Python 支持 | 有 | 有 | 没有 |
性能 | 中 | 高 | 高 |
🎯 实战结论:
- 99% 用 DataFrame / Spark SQL
- Python 没有 Dataset,不用纠结
- DataFrame 底层最终还是 RDD
⚠️ 关于 DataFrame 比 RDD 快的陷阱
❌ 错误认知:"DataFrame 用内存,RDD 用磁盘"
✅ 正确认知:DataFrame 快是因为:
- Schema → Catalyst 优化(谓词下推、列裁剪等)
- Tungsten 内存优化(紧凑二进制格式,比 JVM 对象省一半内存)
- Whole-stage Codegen(多个算子编译成一个函数)
3.4 Catalyst:Spark SQL 的大脑
🧠 Catalyst 的四阶段工作流程(必背)
阶段 | 做的事 | 报错时机 |
Parse | SQL → 语法树 | 语法错 |
Analyze | 查 Hive Metastore,绑定表/字段/类型 | 字段不存在 |
Optimize | RBO + CBO 优化 | - |
Physical Plan | 选择具体执行方式(Join 策略等) | - |
🎯 口诀:解析 → 分析 → 优化(⭐最核心的一步) → 物理计划
🔧 RBO vs CBO 两大优化策略
ㅤ | RBO(规则优化) | CBO(成本优化) |
依据 | 预设规则 | 表的真实统计信息 |
前提 | 无 | 需先 ANALYZE TABLE |
例子 | 谓词下推、列裁剪 | Join 顺序调整 |
常见优化规则:
- 谓词下推:filter 推到数据源最近处
- 列裁剪:只读用到的列
- 分区裁剪:只扫用到的分区
- 常量折叠:
10+8编译期变成18
🎯 Catalyst 只对 DataFrame/SQL 有效,对 RDD 无效——这是"用 DataFrame 不用 RDD"的根本原因。
3.5 宽依赖 vs 窄依赖
📐 定义与算子分类
- 窄依赖:父 RDD 一个分区 → 子 RDD 最多一个分区使用(一对一)
- 宽依赖:父 RDD 一个分区 → 子 RDD 多个分区使用(触发 Shuffle)
算子归类:
类型 | 算子 |
窄依赖 | map, filter, flatMap, mapPartitions, union, coalesce(减少分区) |
宽依赖 | groupByKey, reduceByKey, distinct, sortByKey, repartition, join(默认) |
⚠️ 5 个容易踩坑的陷阱
union是窄依赖(只拼分区不洗牌)
coalesce减少分区是窄依赖;repartition永远是宽依赖
distinct是宽依赖(底层是reduceByKey)
join不一定是宽依赖:预分区一致时是窄依赖(Co-partitioned Join)
- 窄依赖不触发 Shuffle
3.6 Shuffle:Spark 最大性能瓶颈
🌪️ Shuffle 是什么,为什么慢
定义:跨节点重新分发数据的过程。宽依赖必然触发 Shuffle。
两阶段:
- Shuffle Write:上游 Task 按 key 分桶写本地磁盘
- Shuffle Read:下游 Task 通过网络从各上游拉数据
4 大开销:
- 磁盘 I/O
- 网络 I/O
- 序列化/反序列化
- 容易数据倾斜
🛡️ 减少 Shuffle 的 5 个方法
reduceByKey代替groupByKey(Map 端预聚合)
- 广播小表(Broadcast Join)
- 合理设置
spark.sql.shuffle.partitions(默认 200)
- 避免重复 Shuffle
- 预分区数据(Bucketed Join)
3.7 Map 端预聚合(Day 2 补讲)
🎯 为什么 reduceByKey 比 groupByKey 快
核心:Shuffle 前每个分区先局部聚合,减少 Shuffle 数据量。
对比示例(3 分区求每个 user 的 amount 总和):
方式 | Shuffle 数据 | 原因 |
groupByKey().mapValues(sum) | 全部原始记录 | 无预聚合 |
reduceByKey(_+_) | 预聚合后结果 | Map 端先局部合并 |
为什么
groupByKey 不能预聚合?因为它只说"分组",没给聚合函数。
reduceByKey 给了聚合函数,Spark 才能在本地提前聚合。🌍 类比:全国人口普查
groupByKey:14 亿人全送北京再数 → 14 亿人在路上
reduceByKey:每村 → 每县 → 每省逐级汇总 → 只有 34 个数字送北京
🎯 死规则:能用
reduceByKey 绝不用 groupByKey3.8 懒执行(Lazy Evaluation)⭐今日重点
😴 什么是懒执行
定义:Transformation 算子调用时只记录逻辑(构建 DAG),不立刻执行;遇到 Action 时才一次性触发。
日常类比:
- 急性子助理(急切执行):每吩咐一句立刻跑一趟,跑 6 趟
- 聪明助理(懒执行):攒到最后一次性规划 + 执行,只跑 1 趟
🎯 核心价值:攒着一起看才能做 Catalyst 全局优化(谓词下推、列裁剪)。
🔍 如何判断代码是不是懒执行(实战技巧)
判断法:看返回值类型
返回类型 | 判断 |
DataFrame / RDD | 🟢 懒(Transformation) |
具体值(int/list/None) | 🔴 Action |
打印到屏幕 / 写文件 | 🔴 Action |
必背 Action 清单(就这些,其他全是懒的):
DataFrame:
RDD:
🎯 识别口诀:能让数据离开 Spark 的就是 Action;只在 Spark 内部变形的就是 Transformation。
⚠️ 懒执行的 5 个关键推论
- 代码不报错不代表没错:错误等 Action 触发才暴露
- 打印 DataFrame 啥也看不到:只显示 Schema,要
show()才显示数据
- 每次 Action 都从头重算:文件会被重复读(→ 用
cache())
cache()本身也是懒的:第一个 Action 触发时才真正物化
- 测时间要包到 Action 里:不然测到的是 0.001 秒(骗你的)
3.9 Cache / Persist(缓存)
💾 cache vs persist 使用指南
为什么要缓存?
用 cache 后:
常见存储级别:
级别 | 说明 |
MEMORY_ONLY | 纯内存,放不下丢(cache 默认) |
MEMORY_AND_DISK | 内存不够放磁盘 ⭐生产常用 |
DISK_ONLY | 只落盘 |
🎯 陷阱:
cache()=persist(MEMORY_ONLY)的简写
cache()本身是懒的
- 用完记得
unpersist()
3.10 Job / Stage / Task 四层级
🏗️ Spark 执行单元的嵌套关系
核心规则:
- 1 个 Action = 1 个 Job
- Stage 划分:从后往前扫 DAG,遇宽依赖就切
- Stage 的 Task 数 = 该 Stage 最后一个 RDD 的分区数
- 同一 Stage 内窄依赖算子合并成 pipeline 一气呵成
📊 为什么要切 Stage
宽依赖前后数据布局不同(key 重新分区了),不能在同一个 Task 里连续跑。必须切断:
- Stage 内部:数据流水线,不落盘
- Stage 之间(宽依赖处):Shuffle,落盘
🎯 这就是 Spark 比 MR 快的核心:MR 每步都落盘;Spark 只在 Stage 间落盘。
3.11 DAG 调度
🔗 DAG 是什么
DAG = Directed Acyclic Graph(有向无环图)
- Directed:每条线有方向
- Acyclic:不能有环
- Graph:节点 + 边
Spark 把整个作业画成一张 DAG:
- 节点 = 计算步骤
- 边 = 数据流向
两大调度器:
角色 | 职责 |
DAGScheduler | 按宽依赖把 DAG 切成 Stage |
TaskScheduler | 把 Task 分配给 Executor |
🎯 DAG + 懒执行 + Catalyst,三者共同构成 Spark 快于 MR 的底层原理。
3.12 Join 策略(必考)
🤝 三种 Join 策略对比
策略 | 原理 | 适用 | 是否 Shuffle |
BroadcastJoin | 小表广播到每台机器 | 小表 < 几百 MB | ✅ 无 Shuffle |
SortMergeJoin | 两表按 key 排序后合并 | 大表 vs 大表 | 有 |
ShuffleHashJoin | Shuffle 后建 Hash 表 | 中等规模 | 有 |
🎯 Catalyst 自动选:
- 一表很小 → BroadcastJoin
- 两表都大 → SortMergeJoin
优化建议:
- 开启 AQE(Spark 3.0+ 运行时自适应优化)
- 提前分桶(Bucketed Join 免 Shuffle)
- 调大
spark.sql.shuffle.partitions
3.13 数据倾斜基础(Day 3 深入)
⚖️ 数据倾斜识别与应对
症状:
- 少数 Task 远慢于其他 Task(99% Task 1 分钟,1% Task 1 小时)
- 作业卡尾部
- OOM
常见场景:
- 爆款商品占订单 50%
- null / 默认值大量存在
- 热点地区 / 时段
应对方法:
- 过滤 null / 异常值
- 加盐:给倾斜 key 加随机前缀打散
- Map 端聚合(reduceByKey)
- 广播小表
- 倾斜 key 单独处理
4️⃣ Day 1 错题巩固
📅 拉链表 SQL 边界(错点 ①②)
🎯 记死三件套:
<= + >= + 9999-12-31🎯 事实表 vs 维度表(错点 ③)
3 把尺子:
维度 | 事实表 | 维度表 |
一行代表 | 一次事件 | 一个实体 |
字段 | 度量值 + 外键 id | 描述性属性 |
时间 | 强时间戳 | 弱 |
变化 | 只增 | 慢变 |
关键认知:
- "描述谁" ≠ 维度表。维度描述实体属性,事实记录发生了什么
- 名字带 "detail" 的大概率是事实表("明细" = 最细粒度事实)
- 带一堆
xxx_id+ 度量值的是事实表典型长相
📝 数仓术语精度(错点 ⑤)
三层词汇不能混:
层次 | 例子 |
原始事实字段 | quantity, price |
派生指标 | gmv = price × quantity |
聚合指标 | order_cnt = count(*) |
❌ 不要说"事实加维度"(数仓无此概念)
✅ 说"聚合事实表"(度量被聚合后的产物)
5️⃣ Day 2 错题复盘
❌ Q3:Action / Job / Cache 联动(错得典型)
原代码:
正确答案:
- 读文件次数:1 次(cache 生效后,后两个 Action 用缓存)
- Job 数:3 个(每个 Action 一个 Job)
- cache 生效时机:
r1 = df3.count()第一个 Action 触发时
- 无 cache 情况:读 3 次(每个 Action 从头重算)
🎯 核心概念:
- 1 Action = 1 Job
- 不 cache → 每个 Action 从头重算
- cache 是懒的,第一个 Action 触发才真正物化
❌ Q5:Map 端预聚合(Claude 漏讲了)
版本 A:
groupByKey().mapValues(sum) ❌ 慢版本 B:
reduceByKey(_+_) ✅ 快原因:B 有 Map 端预聚合,Shuffle 数据量小得多。
🎯 死规则:能用
reduceByKey 绝不用 groupByKey❌ Q7:大表 Join 大表(完全没答)
场景:100 GB + 10 TB 按 user_id join
策略:SortMergeJoin(BroadcastJoin 装不下 100 GB)
代码:
优化方向:
- 开启 AQE
- 提前按 user_id 分桶
- 调大 shuffle 分区数
❌ Q8:TopN SQL(SQL 写错 3 处)
错点:
PARTITION BY user_id错 → 应该是PARTITION BY dt
- 多余逗号语法错
- 漏了
WHERE rank <= N
TopN 固定模板(死记):
三要素:
PARTITION BY= "每个 X" 里的 X
ORDER BY ... DESC= 排序依据
- 外层
WHERE rank <= N= 卡 TOP N
6️⃣ 我问过的好问题(知识点延伸)
❓ "order_detail 描述 order,为什么不是维度表?"
答:维度表的"描述"是描述实体属性(长啥样),事实表记录发生了什么(业务事件)。
order_detail记录"某用户某时刻买了某商品" → 是事件 → 事实表
dim_order(假想)描述"订单类型、渠道、是否礼品订单" → 是属性 → 维度表
🎯 判断口诀:看字段不看名字。有 price、quantity 就是度量,就是事实表。
❓ "Spark 是数据仓库吗?"
答:不是。Spark 是计算引擎,只负责"算",不负责"存"。
层次 | 代表 |
存储 | HDFS |
数仓 | Hive |
计算 | Spark |
语言 | SQL |
🎯 大厂组合:Spark on Hive(Spark 计算 + Hive 元数据 + HDFS 存储)
❓ "SQL 扮演什么角色?"
答:SQL 是一种声明式语言(只说要什么,不说怎么做),不是一个产品。
- 同一段 SQL 可以给 MySQL / Hive / Spark / Flink 执行
- 各引擎解析 + 执行路径完全不同
- 它是"人与引擎之间的翻译层"
🎯 类比:SQL 是菜单上的菜名,不同餐厅(MySQL / Spark)做出来味道不同。
❓ "RDD 是数据集吗?"
答:严格讲不是。RDD 是"如何得到数据集的计算描述"(菜谱,不是菜)。
- RDD 默认不持有数据
- Action 触发执行时才真正计算
- cache 时才真正物化到内存
🎯 翻译陷阱:中文"弹性分布式数据集"容易让人以为是装着数据的容器,其实它是计算图。
❓ "RDD 类似没 column 的 csv?能合并大量数据?"
前半句:直觉对,但不精确。
- CSV 每行必须是结构化字段
- RDD 每条可以是任意对象(数字、字典、自定义类)
- 当 RDD 里恰好装 CSV 行字符串时,"给 CSV 加表头 = RDD → DataFrame" 这个类比成立
后半句:❌ 错。RDD 核心不是"合并",是"分散并行处理"。
- 100 TB 数据留在 1000 台机器上各自算
- 只在必要时(GroupBy/Join)才跨机器汇总(Shuffle,慢)
🎯 核心:RDD 不是把数据合起来算,而是让数据散着算。
❓ "Catalyst 又是什么?"
答:Spark SQL 的"大脑",负责把 SQL / DataFrame 翻译 + 优化成能跑的执行计划。
4 阶段:Parse → Analyze → Optimize → Physical Plan
关键认知:Catalyst 只对 DataFrame / SQL 有效,对 RDD 无效。这就是"用 DataFrame 不用 RDD"的根本原因。
❓ "Hive 不是也有 SQL 引擎吗,怎么又说 Spark 用自己的?"
答:Hive 有两个可独立模块:
- Hive SQL 引擎(翻译 SQL)
- Hive Metastore(管元数据)
Spark on Hive 只借用 Metastore,SQL 解析用自己的 Catalyst(更快)。
🎯 一句话:Spark 瞧不上 Hive 的 SQL 引擎,但离不开 Hive 的 Metastore。
❓ "懒执行到底是什么?怎么判断?"
懒执行:Transformation 只记录逻辑,Action 才触发执行。
判断法:
- 返回 DataFrame/RDD → 懒
- 返回具体值 / 打印 / 写文件 → Action
必背 Action:
show, count, collect, take, first, foreach, write.xxx🎯 口诀:能让数据离开 Spark 的就是 Action。
7️⃣ 今日核心概念关系网
🕸️ 所有概念的互联关系(必看)
主干线:
懒执行贯穿一切:
性能优化四件套:
容错链路:
8️⃣ Day 3 预告
📅 明天重点
基于今日暴露的问题,Day 3 将深入:
- Shuffle 原理深入 + 数据倾斜 7 大解法(承接 Q4)
- Join 策略完整剖析(承接 Q7)
- Job / Stage / Task 三层级深入(承接 Q3、Q6)
- SQL 窗口函数集训(承接 Q8,必须拿下)
- Spark 性能调优参数(笔试高频)
🎯 Day 2 三句话记忆
- 大数据技术栈分层:SQL → Catalyst → RDD → Spark → YARN → HDFS;每层各司其职,Spark 不是数仓,只是计算引擎。
- Spark 快于 MR 的三大支柱:懒执行(攒着一起优化)+ Catalyst(自动优化)+ DAG 调度(按宽依赖切 Stage,减少落盘)。
- 实战优先级:99% 用 DataFrame/SQL,不用 RDD;能用
reduceByKey不用groupByKey;多次用的 DataFrame 必须cache;大表 Join 小表用 Broadcast。