docs:重写README,修正课程编号与项目结构

修正点:第8课实际是cache/persist/checkpoint(README误写为flatMap);第9课尚未开课但README已写描述;项目结构补充exercises4~8、org.example.sql、App2、hive-site.xml;spark-submit注释里类路径 org.example.core.App2 改回 org.example.App2。新增安全提醒章节记录MySQL凭据明文问题。
This commit is contained in:
2026-09-21 00:41:46 +08:00
parent d956be7154
commit 95edd82f9d
+174 -454
View File
@@ -2,27 +2,20 @@
> 仓库:[`KennethCheng/spark_20260915`](https://gitea.kennethcheng.cc/KennethCheng/spark_20260915)
这是我的 **Scala + Spark 学习与练习项目**,主要用于记录跟随课程学习 Spark 的过程,并把课堂 Demo、课后练习和实际运行实验整理到同一个 Maven 项目中。
我的 **Scala + Java 双实现 Spark 学习项目**,统一放在一个 Maven 工程里。代码分两条线:
当前学习重点以 **Spark Core / RDD** 为主,并逐步过渡到 **Spark SQL / DataFrame / Hive / JDBC**。
- `chapter` —— 线下课程 Demo(`org.example.*`):核心算子 / 分区 / 缓存 / Checkpoint / Spark SQL / Spark On Hive
- `practise` —— ChatGPT 课程练习(`org.exercises.exercisesN.*`):每课一目录,含 `demo/` 演示 + `exercises/` 练习题
主线按 **Spark 2.4.3 / Scala 2.11 / JDK 8** 学习,从 RDD 入门到 Spark SQL / Hive / JDBC / Yarn 提交。
---
## 学习方式
## ⚠️ 安全提醒(先看)
项目中的代码主要分成两类:
- `chapter`:线下课程内容、课堂 Demo、知识点验证
- `practise`:配合 ChatGPT 学习完成的课后练习
代码统一放在 Maven 的 `src/main/scala` / `src/main/java` 下,尽量保持一个知识点一个 Demo,方便单独运行和复习。
提交信息统一使用中文,例如:
```text
feat:线下课:集合_...
feat:ChatGPT第 7 课:缓存 Cache / Persist + mapPartitions
```
- `src/main/resources/hive-site.xml` 与 `src/main/scala/org/example/sql/*.scala` 中含有**明文 MySQL 账号密码**与内网 JDBC URL(参见审查报告)
- 当前仓库是公开的 Gitea,凭据已暴露在 git 历史中,**先轮换密码再继续**
- AGENTS.md 已要求凭据外部化(环境变量 / `*.local.properties` + `.gitignore`),本次重写 README 不修代码,仅做记录
---
@@ -30,9 +23,9 @@ feat:ChatGPT第 7 课:缓存 Cache / Persist + mapPartitions
| 类别 | 版本 |
| --- | --- |
| JDK | Java 8 |
| JDK | 8 |
| Scala | 2.11.12 |
| Apache Spark | 2.4.3 |
| Apache Spark | 2.4.3(core / sql / hive) |
| Hadoop Client | 2.7.3 |
| Maven | Maven 3 |
| scala-maven-plugin | 4.8.1 |
@@ -40,8 +33,7 @@ feat:ChatGPT第 7 课:缓存 Cache / Persist + mapPartitions
| MySQL Connector | 8.0.28 |
| HikariCP | 4.0.3 |
| commons-dbutils | 1.6 |
> 项目以 **Spark 2.4.3 + Scala 2.11 + JDK 8** 为基准,运行环境不要随意升级 JVM / Scala 版本。
| JUnit | 3.8.1(test scope,当前无用例) |
---
@@ -50,250 +42,97 @@ feat:ChatGPT第 7 课:缓存 Cache / Persist + mapPartitions
```text
spark_project/
├── pom.xml
├── AGENTS.md
├── AGENTS.md # Agent / 协作规约(必读)
├── README.md
│
├── src/main/java/org/example/
│ ├── core/
│ │ ├── GroupByKeyDemo.java
│ │ ├── MyPartitioner.java
│ │ ├── RddCreateDemo.java
│ │ └── RepartitionVsCoalesce.java
│ └── AppTest.java
│
├── src/main/scala/org/example/
│ ├── App.scala
│ ├── test.scala
├── src/main/java/org/example/ # Java 版核心 Demo
│ ├── AppTest.java
│ └── core/
│ ├── GroupByKeyDemo.scala
│ ├── MyPartitioner.scala
│ ├── RddCreateDemo.scala
│ └── RepartitionVsCoalesce.scala
│ ├── GroupByKeyDemo.java
│ ├── MyPartitioner.java
│ ├── RddCreateDemo.java
│ └── RepartitionVsCoaleasce.java
│
├── src/main/scala/org/exercises/
│ ├── exercises1/
│ ├── exercises2/
│ ├── exercises3/
│ └── ...
├── src/main/scala/org/example/ # Scala 版核心 Demo(与 Java 同名 4 个 + 扩展)
│ ├── App.scala # local[2] 入口:word.txt WordCount
│ ├── App2.scala # spark-submit 集群提交示例(注释里有完整命令)
│ ├── test.scala
│ ├── core/
│ │ ├── GroupByKeyDemo.scala
│ │ ├── MyPartitioner.scala
│ │ ├── RddCreateDemo.scala
│ │ ├── RepartitionVsCoalesce.scala
│ │ ├── CacheTest.scala
│ │ ├── DemoCache.scala # cache + persist 性能对比
│ │ ├── DemoCacheNoCache.scala
│ │ ├── CheckpointDemo.scala # setCheckpointDir + checkpoint()
└── ReadFromCheckpoint.scala
│ └── sql/ # Spark SQL 线下课 Demo
│ ├── CreateDataFrame.scala # RDD+Schema / CSV / JDBC 三种方式
│ ├── DataFrameDSL.scala # select / col / as
│ ├── DataFrameSql.scala # createTempView + spark.sql
│ └── SparkOnHive.scala # enableHiveSupport + spark.sql
│
├── src/main/scala/org/exercises/ # ChatGPT 课程练习(按课号递增)
│ ├── exercises1/ # 第 1 课:Spark 入门
│ │ ├── demo/ (DemoS1tart / Demo2CreateRDD / Demo3RddMap)
│ │ └── exercises/ (Exercises1~5)
│ ├── exercises2/ # 第 2 课:RDD 算子 map / filter / flatMap / reduce / foreach
│ ├── exercises3/ # 第 3 课:PairRDD / reduceByKey / WordCount
│ ├── exercises4/ # 第 4 课:groupByKey vs reduceByKey / Shuffle
│ ├── exercises5/ # 第 5 课:Partition / partitionBy / 自定义 Partitioner
│ ├── exercises6/ # 第 6 课:窄依赖 / 宽依赖 / Stage / Task
│ ├── exercises7/ # 第 7 课:cache / persist / mapPartitions
│ └── exercises8/ # 第 8 课:cache / persist / checkpoint
│
└── src/main/resources/
├── create_table.sql
├── create_table.sql # 离线分析结果目标表(top_ten_avg_ratings / genres_average_rating)
├── hive-site.xml # ⚠️ 含明文凭据,待外部化
├── log4j.properties
└── word.txt
├── word.txt # WordCount 示例输入
└── spark/sql/student.csv # DataFrame 读取示例
```
> `src/main/resources/tmp/spark-checkpoint/` 是运行时产物,已被运行时写入但**不应进仓库**,后续会加入 `.gitignore`。
---
## Spark 学习进度
## ChatGPT 课程进度
### 第 1 课:Spark 入门
| 课号 | 主题 | 目录 | 演示 Demo | 状态 |
| --- | --- | --- | --- | --- |
| 1 | Spark 入门(Conf / Context / parallelize / textFile / Transformation vs Action) | `exercises1` | `DemoS1tart`、`Demo2CreateRDD`、`Demo3RddMap` | ✅ |
| 2 | RDD 算子:map / filter / flatMap / reduce / foreach | `exercises2` | `Demo1Map` ~ `Demo5Foreach` | ✅ |
| 3 | PairRDD / mapValues / reduceByKey / groupByKey / WordCount | `exercises3` | `Demo1Map` ~ `Demo5WordCount` | ✅ |
| 4 | groupByKey vs reduceByKey / Shuffle | `exercises4` | `Demo1GroupByKeyAndReduceByKey`、`Demo2GroupByKeyVsReduceByKey` | ✅ |
| 5 | Partition / `partitionBy` / 自定义 `Partitioner` / repartition / coalesce | `exercises5` | `Demo1PartitionBasic` ~ `Demo4MyPartitioner` | ✅ |
| 6 | 窄依赖 / 宽依赖 / Stage / Task / DAG | `exercises6` | `Demo.scala` | ✅ |
| 7 | Cache / Persist / `mapPartitions` / 性能对比 | `exercises7` | `Demo1CacheAndPersist`、`Demo2MapPartitions` | ✅ |
| 8 | cache / persist / `setCheckpointDir` / checkpoint 复用 | `exercises8` | `Demo1Checkpoint` | ✅ |
主要内容:
- `SparkConf`
- `SparkContext`
- `parallelize`
- 从文件创建 RDD
- Spark 程序基本运行方式
- Transformation / Action 基本概念
对应练习:
```text
src/main/scala/org/exercises/exercises1/
```
每课 5 道练习,统一以 `Exercises1.scala` ~ `Exercises5.scala` 命名,可独立 `main` 运行。
---
### 第 2 课:RDD 常用算子
## 线下课 Demo 一览(`org.example.*`)
主要学习:
| 包 | Demo | 说明 |
| --- | --- | --- |
| `org.example` | `App` | `local[2]` 跑 `word.txt` 的 WordCount 入门入口 |
| `org.example` | `App2` | 集群 `spark-submit` 模板,注释里写好了完整提交命令 |
| `org.example.core` | `RddCreateDemo` | `parallelize` + `textFile` 两种 RDD 创建方式 |
| `org.example.core` | `GroupByKeyDemo` | `groupByKey` 行为与输出观察 |
| `org.example.core` | `RepartitionVsCoalesce` | `repartition` vs `coalesce` 区别 |
| `org.example.core` | `MyPartitioner` | 自定义 `Partitioner` |
| `org.example.core` | `DemoCache` / `DemoCacheNoCache` / `CacheTest` | `cache` / `persist(MEMORY_ONLY)` 是否真的更快 |
| `org.example.core` | `CheckpointDemo` / `ReadFromCheckpoint` | `setCheckpointDir` + 复用 checkpoint 切断 lineage |
| `org.example.sql` | `CreateDataFrame` | RDD+Schema / CSV / JDBC 三种建 DF 方式 |
| `org.example.sql` | `DataFrameDSL` | DSL:`select` / `col` / `as` |
| `org.example.sql` | `DataFrameSql` | `createTempView` + `spark.sql` |
| `org.example.sql` | `SparkOnHive` | `enableHiveSupport()` + `spark.sql("use gmall")` |
| `org.example` | `test.scala` | 随手调试用,可忽略 |
- `map`
- `filter`
- `flatMap`
- `reduce`
- `foreach`
重点理解:
```text
Transformation
↓
生成新的 RDD
↓
继续构建计算链
Action
↓
触发 Job 执行
↓
返回结果 / 输出结果
```
对应练习:
```text
src/main/scala/org/exercises/exercises2/
```
---
### 第 3 课:PairRDD / WordCount
主要学习:
- `(K, V)` 键值对 RDD
- `map` 生成 PairRDD
- `reduceByKey`
- WordCount
- `groupByKey`
- shuffle 的基本概念
典型流程:
```text
文本
↓
flatMap
↓
map
↓
reduceByKey
↓
collect
```
对应练习:
```text
src/main/scala/org/exercises/exercises3/
```
---
### 第 4~5 课:Scala 函数基础 + Spark 分区
Scala 部分重点:
- `def`
- `=>`
- `map`
- 函数作为参数
- `_` 下划线简写
- Lambda / 函数值
- 函数参数与返回值
Spark 部分重点:
- Partition
- `getNumPartitions`
- `glom`
- `mapPartitions`
- `partitionBy`
- 自定义 `Partitioner`
- `groupByKey`
- `reduceByKey`
- `repartition`
- `coalesce`
项目中的核心 Demo:
```text
src/main/scala/org/example/core/
src/main/java/org/example/core/
```
---
### 第 6~7 课:RDD 执行过程与缓存
主要内容:
- Transformation 与 Action
- Lazy Evaluation
- DAG
- Job / Stage / Task
- Cache
- Persist
- `mapPartitions`
- 多次 Action 时为什么缓存可能提升性能
- Spark UI 中查看 Job / Stage / Task
重点认识:
```text
Transformation
↓
只记录计算逻辑
↓
Action
↓
真正触发 Job
```
以及:
```text
RDD
├── cache()
└── persist(...)
```
缓存不是“加了就一定更快”,只有同一个 RDD 被重复使用、重新计算成本较高时,收益才比较明显。
---
### 第 8 课:flatMap 与嵌套集合
主要学习:
- `map` 与 `flatMap` 的区别
- 嵌套集合处理
- 一对多转换
- WordCount 中为什么常用 `flatMap`
- 不使用 `flatMap` 的替代实现
典型理解:
```text
map
1 -> List(1, 2)
2 -> List(3, 4)
结果:
List(List(1, 2), List(3, 4))
```
```text
flatMap
1 -> List(1, 2)
2 -> List(3, 4)
结果:
List(1, 2, 3, 4)
```
---
### 第 9 课:排序与分组
主要学习:
- `sortBy`
- `sortWith`
- `groupBy`
- 分组后 `Iterable / CompactBuffer` 的理解
- 排序与分组的使用场景
- PairRDD 与普通 RDD 的进一步操作
常见写法:
```scala
rdd.sortBy(_._2)
rdd.sortWith((a, b) => a._2 > b._2)
rdd.groupBy(_ % 2)
```
> `org.example.core.*` 在 `src/main/java` 下还有 4 个 Java 版同名 Demo,方便对照两种语言写法。
---
@@ -301,44 +140,38 @@ rdd.groupBy(_ % 2)
```text
Spark
│
├── SparkConf / SparkContext
├── SparkConf / SparkContext / SparkSession
│
├── RDD
│ ├── 创建
│ │ ├── parallelize
│ │ ├── parallelize(makeRDD)
│ │ └── textFile
│ │
│ ├── Transformation
│ │ ├── map
│ ├── Transformation(Lazy)
│ │ ├── map / mapPartitions
│ │ ├── filter
│ │ ├── flatMap
│ │ ├── mapPartitions
│ │ ├── groupBy
│ │ ├── sortBy
│ │ ├── sortWith
│ │ └── reduceByKey
│ │ ├── groupBy / sortBy / sortWith
│ │ └── reduceByKey / groupByKey
│ │
│ ├── Action
│ │ ├── collect
│ │ ├── count
│ │ ├── reduce
│ │ └── foreach
│ ├── Action(触发 Job)
│ │ ├── collect / count / take
│ │ ├── reduce / foreach
│ │ └── saveAsTextFile
│ │
│ ├── Partition
│ │ ├── glom
│ │ ├── getNumPartitions
│ │ ├── partitionBy
│ │ ├── repartition
│ │ └── coalesce
│ │ ├── getNumPartitions / glom
│ │ ├── partitionBy(自定义 Partitioner)
│ │ ├── repartition ← 触发 Shuffle
│ │ └── coalesce ← 默认不 Shuffle
│ │
│ └── 性能
│ ├── cache
│ ├── persist
│ └── Lazy Evaluation
│ ├── Lazy Evaluation
│ ├── cache / persist(StorageLevel)
│ └── checkpoint(切断 lineage,依赖可靠文件系统)
│
└── PairRDD
├── reduceByKey
├── reduceByKey / foldByKey
├── groupByKey
└── 自定义 Partitioner
```
@@ -347,261 +180,147 @@ Spark
## Spark SQL / DataFrame / Hive
RDD 基础阶段完成后,项目继续进入 Spark SQL 相关内容:
RDD 基础阶段完成后,开始 Spark SQL:
- `SparkSession`
- DataFrame
- DataSet 基础概念
- Schema
- DSL 查询
- SQL 查询
- JDBC
- Hive 支持
- Spark Thrift Server
- DBeaver 连接 Spark SQL
- DataFrame 与 JDBC 数据写入
目前已经开始接触:
```scala
val spark = SparkSession.builder()
.appName("Spark On Hive")
.enableHiveSupport()
.getOrCreate()
```
以及:
```scala
spark.sql("show databases")
spark.sql("show tables")
spark.sql("select ...")
```
后续会把 RDD 学到的思想逐步对应到 DataFrame / SQL 中。
- `SparkSession.builder().enableHiveSupport().getOrCreate()`
- DataFrame 创建:RDD+Schema / `spark.read.csv(...)` / `spark.read.format("jdbc")`
- DSL:`select` / `col` / `as`
- SQL:`createTempView` + `spark.sql(...)`
- Hive:`spark.sql("use gmall")` + 聚合查询
- 目标:DataFrame 聚合结果 → JDBC → MySQL(建表 SQL 在 `create_table.sql`)
---
## Spark 集群实践
除了本地 `local` 模式,还会进行 Spark 集群环境实验。
| 项目 | 说明 |
| --- | --- |
| `spark-submit` | `App2.scala` 注释里给出完整 yarn-client 提交命令 |
| Spark UI | 本地 `local[2]` 跑起来后 `http://localhost:4040`(Jobs / Stages / Storage / SQL) |
| History Server | `18080`(仅历史任务) |
| Yarn Client | `--master yarn --deploy-mode client` |
| HDFS | `textFile("hdfs://...")` 见 `RddCreateDemo` 注释 |
| Hive / Beeline / DBeaver | 已跑通 `enableHiveSupport` 链路 |
已经实践过的内容包括:
注意:
- `spark-submit`
- Yarn Client
- HDFS
- Spark UI
- Spark History Server
- Hive / Hive Metastore
- Spark Thrift Server
- Beeline
- DBeaver
- Spark SQL
例如:
```bash
spark-submit \
--master yarn \
--deploy-mode client \
--class org.example.core.App2 \
spark_project-1.0-SNAPSHOT.jar
```
- `4040` 是当前 Application 的实时 UI,结束即失效
- `18080` 是 History Server,两者不是一回事
---
## 快速开始
### 1. 准备环境
### 1. 环境
确保本机具备:
JDK 8、Scala 2.11.x、Maven 3.x,本地能 `mvn` 即可。
```text
JDK 8
Scala 2.11.x
Maven 3.x
```
### 2. 编译
### 2. 拉依赖与编译
```bash
mvn -DskipTests dependency:resolve
mvn compile
```
### 3. 打包
### 3. 打包(可选)
```bash
mvn package
```
生成:
产物:
```text
target/spark_project-1.0-SNAPSHOT-jar-with-dependencies.jar
```
### 4. IDEA 运行
### 4. 跑单个 Demo
在 IntelliJ IDEA 中直接运行 Scala / Java 中的 `object` 或 `main` 方法即可。
任选其一:
- IDE 里把目标 `object ExercisesX` / `object Xxx` / `class Xxx` 当作 `main` 直接 Run
- 命令行:
```bash
java -cp target/spark_project-1.0-SNAPSHOT-jar-with-dependencies.jar org.example.App
```
集群提交参考 `App2.scala` 顶部注释。
---
## 本地 Spark UI
## MySQL / Hive 配置
本地运行 Spark 程序时,可以通过 Spark Web UI 查看:
- JDBC 表结构:`src/main/resources/create_table.sql`
- `top_ten_avg_ratings` —— Top10 平均评分电影
- `genres_average_rating` —— 类型平均评分
- Hive:`src/main/resources/hive-site.xml`(⚠️ 内含明文凭据,待外部化)
- 依赖:`mysql-connector-java` / `HikariCP` / `commons-dbutils`
凭据外部化建议(参考 AGENTS.md):
```text
http://localhost:4040
```
src/main/resources/
├── application.properties.example # 模板:仅占位符,提交到仓库
└── application.local.properties # 真凭据,gitignore
常见页面:
```text
Jobs
Stages
Storage
Environment
Executors
SQL
```
注意:
- `4040` 是当前 Spark Application 的 Web UI 端口
- Application 结束后,4040 通常就不再提供该 Application 的实时页面
- `18080` 是 Spark History Server,不是本地运行 Application 的实时 UI
---
## MySQL / JDBC
`src/main/resources/create_table.sql` 中保留了后续分析结果的目标表。
目前主要规划:
```text
Spark DataFrame
↓
聚合分析
↓
JDBC 写入
↓
MySQL
```
目标表:
| 表名 | 用途 |
| --- | --- |
| `top_ten_avg_ratings` | Top10 平均评分电影 |
| `genres_average_rating` | 类型平均评分 |
相关依赖:
```text
mysql-connector-java
HikariCP
commons-dbutils
读取:
val props = new Properties()
props.load(getClass.getResourceAsStream("/application.properties"))
props.load(getClass.getResourceAsStream("/application.local.properties"))
```
---
## 代码规范
## 代码规范与提交流程
### Demo 命名
按课程 / 知识点拆分 Demo,尽量保持:
- 单文件尽量一个知识点,命名沿用 `DemoXxx` / `ExercisesX` / `XxxDemo`
- 中文注释 + **中文 commit message**
- 推荐样式:
```text
Demo1xxx
Demo2xxx
...
```
### Commit
推荐:
```text
feat:线下课:集合_...
feat:线下课:集合_xxx
feat:ChatGPT 第 7 课:缓存 Cache / Persist + mapPartitions
fix:修复 ...
docs:更新 README
refactor:重构 ...
```
每个知识点尽量保持一个独立 commit,便于后续按课程回看。
- 单课 / 单练习保持原子提交
- `main` 分支受保护,从 `main` 拉 feature 分支再 PR(详见 AGENTS.md)
---
## 学习目标
最终形成一套完整的 Spark 学习路线:
## 学习路线
```text
Scala 基础
↓
RDD 基础
RDD 入门(parallelize / textFile / Transformation vs Action)
↓
PairRDD
RDD 算子(map / filter / flatMap / reduce / foreach)
↓
Partition / Shuffle
PairRDD(reduceByKey / groupByKey / WordCount)
↓
Cache / DAG / Stage / Task
Partition / Shuffle / 自定义 Partitioner
↓
Spark SQL / DataFrame
依赖 / DAG / Stage / Task
↓
Hive
Cache / Persist / Checkpoint
↓
JDBC
Spark SQL(DataFrame / DSL / SQL)
↓
Spark Thrift Server
Hive(enableHiveSupport / Metastore)
↓
Yarn / 集群运行
JDBC 读写 MySQL
↓
Spark Thrift Server / Beeline / DBeaver
↓
Yarn / spark-submit / 集群运行
↓
完整数据分析项目
```
重点不是只记 API,而是逐步理解:
```text
代码怎么写
↓
RDD 怎么产生
↓
Transformation 怎么串起来
↓
Action 什么时候触发
↓
Spark 如何拆成 Job / Stage / Task
↓
数据如何在 Partition 之间流动
↓
Shuffle 为什么发生
↓
为什么某些写法性能更好
```
---
## 学习记录
| 阶段 | 内容 | 状态 |
| --- | --- | --- |
| 01 | Spark 入门 | ✅ |
| 02 | RDD 常用算子 | ✅ |
| 03 | PairRDD / WordCount | ✅ |
| 04 | Scala 函数基础 | ✅ |
| 05 | 函数作为参数 / Partition | ✅ |
| 06 | RDD 执行与惰性计算 | ✅ |
| 07 | Cache / Persist / mapPartitions | ✅ |
| 08 | flatMap / 嵌套集合 | ✅ |
| 09 | sortBy / sortWith / groupBy | ✅ |
| 10+ | Spark SQL / DataFrame / Hive | 🚧 |
---
## 相关资源
@@ -609,3 +328,4 @@ Shuffle 为什么发生
- Apache Spark:<https://spark.apache.org/>
- Apache Hadoop:<https://hadoop.apache.org/>
- Apache Maven:<https://maven.apache.org/>
- Hive 配置文档:<https://cwiki.apache.org/confluence/display/Hive/Configuration+Properties>