第9章 Spark实战
内存计算框架:RDD 是什么、算子怎么选、一个 WordCount 怎么用 Spark 写得比 MapReduce 短十倍。
本章学习目标
- 能说出 Spark 的定位、主要组件与相对 MapReduce 的优势
- 能安装配置 Spark(含 Scala 环境)
- 能理解 RDD 的转化算子、行动算子与依赖关系
- 能用 Java API 完成一个 Spark 统计程序
本章导览
核心内容
9.1
Spark概述
Spark 是基于内存的分布式计算框架,把中间结果放在内存而不是落盘,因此迭代计算与交互式查询快得多。
9.1.2
Spark的主要组件
| 组件 | 作用 |
|---|---|
| Spark Core | RDD、任务调度、内存管理,其他组件的基础 |
| Spark SQL | 结构化数据处理,可用 SQL 查询 |
| Spark Streaming | 微批流处理 |
| Structured Streaming | 基于 DataFrame 的流处理(新) |
| MLlib | 机器学习算法库 |
| GraphX | 图计算 |
Spark 通常运行在 YARN 上,数据仍存放在 HDFS/HBase 中——它替换的是 MapReduce 计算层,不是存储层。
9.2
安装Spark
# 1) 安装 Scala(Spark 用 Scala 编写)
tar -zxvf scala-2.12.15.tgz -C /export/servers/
vi ~/.bashrc
export SCALA_HOME=/export/servers/scala-2.12.15
export PATH=$SCALA_HOME/bin:$PATH
# 2) 解压 Spark 并配置
tar -zxvf spark-3.3.1-bin-hadoop3.tgz -C /export/servers/
cd $SPARK_HOME/conf
cp spark-env.sh.template spark-env.sh
vi spark-env.sh
export JAVA_HOME=/export/servers/jdk1.8.0_141
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export SPARK_MASTER_HOST=hadoop1
# 3) 启动
start-all.sh # 在 $SPARK_HOME/sbin 下(注意与 Hadoop 的 start-all.sh 重名)
# Web:http://hadoop1:8080
# 4) 提交流程
spark-submit --master yarn --class com.demo.Wc \
--jars wc.jar wc.jar hdfs:///input hdfs:///output9.3
Spark RDD
RDD(Resilient Distributed Dataset)是 Spark 的核心抽象:不可变、可分区、可并行计算,并靠"血缘"实现容错。
9.3.1
转化算子
| 算子 | 作用 | 是否触发计算 |
|---|---|---|
| map / flatMap | 逐条转换 / 展开成多条 | 否(惰性) |
| filter | 过滤 | 否 |
| groupByKey | 按 key 分组 | 否(Shuffle) |
| reduceByKey | 按 key 聚合(先本地聚合) | 否(Shuffle) |
| join / union / distinct | 关联、合并、去重 | 否 |
9.3.2
行动算子
| 算子 | 作用 |
|---|---|
| count / collect | 计数 / 收集到驱动端 |
| first / take(n) | 取第一个 / 前 n 个 |
| reduce | 整体聚合 |
| foreach | 逐条处理(常用于写入外部系统) |
| saveAsTextFile | 保存为文件 |
Spark 是惰性执行:转化算子只记录血缘,遇到行动算子才真正提交任务。
9.3.3
RDD依赖关系
- 窄依赖:父 RDD 的一个分区只对应子 RDD 的一个分区(map/filter),无需 Shuffle,快
- 宽依赖:子 RDD 的分区依赖父 RDD 的多个分区(groupByKey/reduceByKey),需要 Shuffle
- DAG 按宽依赖切分 Stage,Stage 内流水线执行
9.4
Spark Java API操作
SparkConf conf = new SparkConf().setAppName("wc").setMaster("local[*]");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile("hdfs://hadoop1:9000/wc/input/w.txt");
JavaRDD<String> words = lines.flatMap(l -> Arrays.asList(l.split("\s+")).iterator());
JavaPairRDD<String, Integer> pairs = words.mapToPair(w -> new Tuple2<>(w, 1));
JavaPairRDD<String, Integer> counts = pairs.reduceByKey((a, b) -> a + b);
counts.saveAsTextFile("hdfs://hadoop1:9000/wc/out-spark");
sc.close();9.5
案例解析——单词统计编程
同一个 WordCount,MapReduce 要写 Mapper、Reducer、Driver 三个类;Spark 用 6 行算子就写完,这就是"表达力"的差别。
| 环节 | MapReduce | Spark |
|---|---|---|
| 代码量 | 3 个类、约 80 行 | 6 行算子 |
| 中间结果 | 落磁盘 | 留内存 |
| 执行速度 | 基准 1× | 通常 10× 以上 |
本章小结
本章讲清 Spark 的定位(内存计算)与组件构成,完成 Scala/Spark 的安装与提交方式,重点讲 RDD 的转化算子、行动算子与窄宽依赖,最后用 Java API 写出 Spark 版 WordCount 并与 MapReduce 版对比。
思考与练习 先自己做,再点开答案
01
转化算子与行动算子的区别?
查看参考答案
转化算子(map/filter/reduceByKey)只构建执行计划、不立即计算,返回新的 RDD;行动算子(count/collect/saveAsTextFile)触发真正的任务提交与计算。
02
窄依赖与宽依赖的区别,为什么宽依赖更贵?
查看参考答案
窄依赖一个子分区只依赖一个父分区,可流水线执行;宽依赖需要 Shuffle,父分区数据要跨节点重分布,涉及磁盘/网络 IO,是性能瓶颈所在。
03
reduceByKey 与 groupByKey 该怎么选?
查看参考答案
优先 reduceByKey:它会在 Map 端先做局部聚合(相当于 Combiner)再 Shuffle,数据量小;groupByKey 把同 key 的所有 value 拉到一个节点,易 OOM。