在讲解Spark之前,我们先了解一下MapReduce的主要缺点。
MapReduce 主要使用磁盘存储计算过程中的数据,虽然可靠性比较高,但是性能却较差 。此外,MapReduce 只能使用 map 和 reduce 函数进行编程,虽然能够完成各种大数据计算,但是编程比较复杂 。而且受 map 和 reduce 编程模型相对简单的影响,复杂的计算必须组合多个 MapReduce job 才能完成,编程难度进一步增加!
于是乎Spark应运而生,Spark 在 MapReduce 的基础上进行了改进,它主要使用 内存 进行中间计算数据存储,加快了计算执行时间,在某些情况下性能可以提升百倍 。除了速度更快,Spark 和 MapReduce 相比,还有更简单易用的编程模型 。Spark 的主要编程模型是 RDD,即弹性数据集 。 在 RDD 上定义了许多常见的大数据计算函数,利用这些函数可以用极少的代码完成较为复杂的大数据计算 。
Spark 是直接针对数据进行编程,将 大规模数据集合抽象成一个 RDD 对象 ,然后在这个 RDD 上进行各种计算处理,得到一个新的 RDD,继续计算处理,直到得到最后的结果数据。所以 Spark 可以理解成是面向对象的大数据计算。我们在进行 Spark 编程的时候,需要思考的是一个 RDD 对象需要经过什么样的操作,转换成另一个 RDD 对象,思考的重心和落脚点都在 RDD 上。
RDD 上定义的函数分两种,一种是转换(transformation) 函数,这种函数的返回值还是 RDD;另一种是 执行(action) 函数,这种函数不再返回 RDD。
RDD 定义了很多转换操作函数,比如有计算 map(func)、过滤 filter(func)、合并数据集 union(otherDataset)、根据 Key 聚合 reduceByKey(func, [numPartitions])、连接数据集 join(otherDataset, [numPartitions])、分组 groupByKey([numPartitions]) 等十几个函数。
作为 Spark 架构核心元素的 RDD。跟 MapReduce 一样,Spark 也是对大数据进行分片计算,Spark 分布式计算的数据分片、任务调度都是以 RDD 为单位展开的,每个 RDD 分片都会分配到一个执行进程去处理。
RDD 上的转换操作又分成两种,一种转换操作产生的 RDD 不会出现新的分片 ,比如 map、filter 等,也就是说一个 RDD 数据分片,经过 map 或者 filter 转换操作后,结果还在当前分片。就像你用 map 函数对每个数据加 1,得到的还是这样一组数据,只是值不同。实际上,Spark 并不是按照代码写的操作顺序去生成 RDD ,比如 rdd2 = rdd1.map(func) 这样的代码并不会在物理上生成一个新的 RDD。 物理上,Spark 只有在产生新的 RDD 分片时候,才会真的生成一个 RDD,Spark 的这种特性也被称作 惰性计算 。
另一种转换操作产生的 RDD 则会产生新的分片 ,比如 reduceByKey,来自不同分片的相同 Key 必须聚合在一起进行操作,这样就会产生新的 RDD 分片。
所以,大家只需要记住,Spark 应用程序代码中的 RDD 和 Spark 执行过程中生成的物理 RDD 不是一一对应的,RDD 在 Spark 里面是一个非常灵活的概念,同时又非常重要,需要认真理解。
和 MapReduce 一样,Spark 也遵循移动计算比移动数据更划算 这一大数据计算基本原则。但是和 MapReduce 僵化的 Map 与 Reduce 分阶段计算相比,Spark 的计算框架更加富有弹性和灵活性,进而有更好的运行性能 。
Spark 会根据程序中的转换函数生成计算任务执行计划,这个执行计划就是一个 DAG 。Spark 可以在一个作业中完成非常复杂的大数据计算 。
所谓 DAG 也就是 有向无环图,就是说不同阶段的依赖关系是有向的, 计算过程只能沿着依赖关系方向执行,被依赖的阶段执行完成之前,依赖的阶段不能开始执行 ,同时,这个依赖关系不能有环形依赖,否则就成为死循环了。下面这张图描述了一个典型的 Spark 运行 DAG 的不同阶段。
在上面的图中, A、C、E 是从 HDFS 上加载的 RDD,A 经过 groupBy 分组统计转换函数计算后得到的 RDD B,C 经过 map 转换函数计算后得到 RDD D,D 和 E 经过 union 合并转换函数计算后得到 RDD F ,B 和 F 经过 join 连接函数计算后得到最终的合并结果 RDD G 。
所以可以看到 Spark 作业调度执行的核心是 DAG,有了 DAG,整个应用就被切分成哪些阶段,每个阶段的依赖关系也就清楚了。之后再根据每个阶段要处理的数据量生成相应的任务集合(TaskSet),每个任务都分配一个任务进程去处理,Spark 就实现了大数据的分布式计算。
具体来看的话,负责 Spark 应用 DAG 生成和管理的组件是 DAGScheduler,DAGScheduler 根据程序代码生成 DAG,然后将程序分发到分布式计算集群,按计算阶段的先后关系调度执行。
大家注意到了么,上面的例子有 4 个转换函数,但是只有 3 个阶段 。那么 Spark 划分计算阶段的依据具体是什么呢?显然并不是 RDD 上的每个转换函数都会生成一个计算阶段 。
通过观察一下上面的 DAG 图,关于计算阶段的划分从图上就能看出规律, 当 RDD 之间的转换连接线呈现多对多交叉连接的时候,就会产生新的阶段。 一个 RDD 代表一个数据集,图中每个 RDD 里面都包含多个小块,每个小块代表 RDD 的一个分片。
一个数据集中的多个数据分片需要进行分区传输,写入到另一个数据集的不同分片中,这种数据分区交叉传输的操作,我们在 MapReduce 的运行过程中也看到过。
这就是 shuffle 过程,Spark 也需要通过 shuffle 将数据进行重新组合,相同 Key 的数据放在一起,进行聚合、关联等操作,因而 每次 shuffle 都产生新的计算阶段。这也是为什么计算阶段会有依赖关系,它需要的数据来源于前面一个或多个计算阶段产生的数据,必须等待前面的阶段执行完毕才能进行 shuffle,并得到数据。
所以大家需要记住,计算阶段划分的依据是 shuffle,不是转换函数的类型 。
思考:
大家可能会想,为什么同样经过 shuffle ,Spark 可以更高效 ?
从本质上看,Spark 可以算作是一种 MapReduce 计算模型的不同实现。Hadoop MapReduce 简单粗暴地根据shuffle 将大数据计算分成 Map 和 Reduce 两个阶段,然后就算完事了。而 Spark 更细腻一点, 将前一个的 Reduce和后一个的 Map 连接起来,当作一个阶段持续计算,形成一个更加优雅、高效的计算模型, 虽然其本质依然是 Map 和Reduce。 但是这种多个计算阶段依赖执行的方案可以有效减少对 HDFS 的访问,减少作业的调度执行次数,因此执行速度也更快。 并且和 Hadoop MapReduce 主要使用磁盘存储 shuffle 过程中的数据不同,Spark 优先使用内存进行数据存储,包 RDD 数据。除非是内存不够用了,否则是尽可能使用内存 , 这也是 Spark 性能比 Hadoop 高的另一个原因。
上面这张图就是 Spark 的运行流程 。
首先,Spark 应用程序启动在自己的 JVM 进程里,即 Driver 进程,启动后调用 SparkContext 初始化执行配置和输入数据。SparkContext 启动 DAGScheduler 构造执行的 DAG 图,切分成最小的执行单位也就是计算任务。
然后 Driver 向 Cluster Manager 请求计算资源,用于 DAG 的分布式计算。Cluster Manager 收到请求以后,将 Driver 的主机地址等信息通知给集群的所有计算节点 Worker。
Worker 收到信息以后,根据 Driver 的主机地址,跟 Driver 通信并注册,然后根据自己的空闲资源向 Driver 通报自己可以领用的任务数。Driver 根据 DAG 图开始向注册的 Worker 分配任务。
Worker 收到任务后,启动 Executor 进程开始执行任务。Executor 先检查自己是否有 Driver 的执行代码,如果没有,从 Driver 下载执行代码,通过 Java 反射加载后开始执行。
➢ Spark Core
Spark Core 中提供了 Spark 最基础与最核心的功能,Spark 其他的功能如:Spark SQL,Spark Streaming,GraphX, MLlib 都是在 Spark Core 的基础上进行扩展的.
➢ Spark SQL
Spark SQL 是 Spark 用来操作结构化数据的组件。通过 Spark SQL,用户可以使用 SQL或者 Apache Hive 版本的 SQL 方言(HQL)来查询数据。
➢ Spark Streaming
Spark Streaming 是 Spark 平台上针对实时数据进行流式计算的组件,提供了丰富的处理数据流的 API。
➢ Spark MLlib
MLlib 是 Spark 提供的一个机器学习算法库。MLlib 不仅提供了模型评估、数据导入等额外的功能,还提供了一些更底层的机器学习原语。
➢ Spark GraphX
GraphX 是 Spark 面向图计算提供的框架与算法库。
目前Spark支持四种部署模式
所谓的 Local 模式,就是不需要其他任何节点资源就可以在本地执行 Spark 代码的环境,一般用于教学,调试,演示等。这里不是我们的重心,不多介绍。
local 本地模式毕竟只是用来进行练习演示的,真实工作中还是要将应用提交到对应的集群中去执行,这里我们来看看只使用 Spark 自身节点运行的集群模式,也就是我们所谓的独立部署(Standalone)模式。Spark 的 Standalone 模式体现了经典的 master-slave 模式。
以下基于Standalone 模式部署Spark集群
tar -zxvf spark-3.0.0-bin-hadoop3.2.tgz -C /opt/module/spark
cd /opt/module/spark
mv spark-3.0.0-bin-hadoop3.2 spark-local
去掉默认配置文件的.template后缀
cd /opt/module/spark/spark-local/conf
此时配置文件的后缀都为.template,这是官方默认的命名,如果此时直接启动Spark,则这些配置文件中的属性都不会生效,所以此时我们需要去掉这些后缀。
for i in *.template;do mv ${i} ${i%.*};done
修改默认配置
1 . 启动集群
1. 执行脚本命令: sbin/start-all.sh
2. 查看三台服务器运行进程: `jpsall`
3. 查看 Master 资源监控 Web UI 界面: http://hadoop104:8080
提交应用
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://hadoop104:7077 \
./examples/jars/spark-examples_2.12-3.0.0.jar \
10
提交参数说明
bin/spark-submit \
--class -class>
--master -url> \
... # other options
-jar> \
[application-arguments]
由于 spark-shell 停止掉后,集群监控 hadoop104:4040 页面就看不到历史任务的运行情况,所以开发时都配置历史服务器记录任务运行情况。
修改 spark-defaults.conf 文件,配置日志存储路径
spark.eventLog.enabled true
spark.eventLog.dir hdfs://hadoop104:9870/directory
注意:需要启动 hadoop 集群,HDFS 上的 directory 目录需要提前存在。
sbin/start-dfs.sh
hadoop fs -mkdir /directory
修改 spark-env.sh 文件, 添加日志配置
export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080
-Dspark.history.fs.logDirectory=hdfs://hadoop104:9870/directory
-Dspark.history.retainedApplications=30"
参数 1 含义:WEB UI 访问的端口号为 18080
参数 2 含义:指定历史服务器日志存储路径
参数 3 含义:指定保存 Application 历史记录的个数,如果超过这个值,旧的应用程序信息将被删除,这个是内存中的应用数,而不是页面上显示的应用数。
分发配置文件: xsync conf
重新启动集群和历史服务
sbin/start-all.sh
sbin/start-history-server.sh
重新执行任务
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://hadoop104:7077 \
./examples/jars/spark-examples_2.12-3.0.0.jar \
10
所谓的高可用是因为当前集群中的 Master 节点只有一个,所以会存在单点故障问题。所以为了解决单点故障问题,需要在集群中配置多个 Master 节点,一旦处于活动状态的 Master发生故障时,由备用 Master 提供服务,保证作业可以继续执行。这里的高可用一般采用 Zookeeper 设置
停止集群: sbin/stop-all.sh
启动 Zookeeper : zk.sh start
修改 spark-env.sh 文件添加如下配置
# 注释如下内容:
#SPARK_MASTER_HOST=hadoop104
#SPARK_MASTER_PORT=7077
# 添加如下内容:
# Master 监控页面默认访问端口为 8080,但是可能会和 Zookeeper 冲突,所以改成 8989,也可以自定义,访问 UI 监控页面时请注意
SPARK_MASTER_WEBUI_PORT=8989
export SPARK_DAEMON_JAVA_OPTS="
-Dspark.deploy.recoveryMode=ZOOKEEPER
-Dspark.deploy.zookeeper.url=hadoop104,hadoop105,hadoop106
-Dspark.deploy.zookeeper.dir=/spark"
分发配置文件: xsync conf/
启动 hadoop105的单独 Master 节点,此时 hadoop105节点 Master 状态处于备用状态: sbin/start-master.sh
提交应用到高可用集群
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://hadoop104:7077,hadoop105:7077 \
./examples/jars/spark-examples_2.12-3.0.0.jar \
10
查看 hadoop105的 Master 资源监控 Web UI,稍等一段时间后,hadoop105 节点的 Master 状态提升为活动状态