目录
1 Spark概念
Spark是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。
1.1与Hadoop对比
Hadoop MapReduce由于其设计初衷并不是为了满足循环迭代式数据流处理,因此在多并行运行的数据可复用场景(如:机器学习、图挖掘算法、交互式数据挖掘算法)中存在诸多计算效率等问题。所以Spark应运而生。
Spark 就是在传统的MapReduce 计算框架的基础上,利用其计算过程的优化,从而大大加快了数据分析、挖掘的运行和读写速度,并将计算单元缩小到更适合并行计算和重复使用的RDD计算模型。
Spark和Hadoop的根本差异是多个作业之间的数据通信问题:Spark多个作业之间数据通信是基于内存,而Hadoop是基于磁盘。
2 Spark核心模块
Spark Core 中提供了Spark最基础和最核心的功能。
Spark SQL是Spark用来操作结构化数据的组件。通过Spark SQL,用户可以使用SQL或者Apache Hive版本的SQL方言(HQL)来查询数据。
Spark Streaming是Spark平台上针对 实时数据进行流式计算 的组件,提供了丰富的处理数据流的API。
Spark MLlib是Spark 提供的一个机器学习算法库。MLlib 不仅提供了模型评估、数据导入等额外的功能,还提供了一些更底层的机器学习原语。
3 Spark运行环境
3.1 本地
3.2 单独部署
3.3 结合Yarn
Spark 查看当前Spark-shell 运行任务情况端口号:4040(计算)
Spark Master内部通信服务端口号:7077
Standalone 模式下,Spark Master Web端口号:8080(资源)
Spark历史服务器端口号:18080
Hadoop YARN任务运行情况查看端口号:8088
国内工作中,将Spark引用部署到Yarn环境中会更多一些,因此总结的提交流程是基于Yam环境的。
独立部署(Standalone)模式由 Spark 自身提供计算资源,无需其他框架提供资源。这种方式降低了和其他第三方资源框架的耦合性,独立性非常强。但是你也要记住,Spark 主要是计算框架,而不是资源调度框架,所以本身提供的资源调度并不是它的强项,所以还是和其他专业的资源调度框架集成会更靠谱一些。
步骤:
①解压缩文件。将 spark-3.0.0-bin-hadoop3.2.tgz 文件上传到 linux 并解压缩,放置在指定位置。
②修改 hadoop 配置文件/opt/module/hadoop/etc/hadoop/yarn-site.xml, 并分发;修改 conf/spark-env.sh,添加 JAVA_HOME 和 YARN_CONF_DIR 配置
③启动集群
④提交应用
⑤配置历史服务器
成功演示如下:
3.4 配置高可用
3.5 容器部署
容器化部署是目前业界很流行的一项技术,基于Docker镜像运行能够让用户更加方便地对应用进行管理和运维。容器管理工具中最为流行的就是Kubernetes(k8s),而Spark也在最近的版本中支持了k8s部署模式。这里我们也不做过多的讲解。
4 Spark运行架构
Spark 框架的核心是一个计算引擎,整体来说,它采用了标准 master-slave 的结构。
Driver 表示 master, 负责管理整个集群中的作业任务调度; Executor 则是 slave,负责实际执行任务。
4.1 Driver
Spark 驱动器节点,用于执行 Spark 任务中的 main 方法,负责实际代码的执行工作。 Driver 在 Spark 作业执行时主要负责:
➢ 将用户程序转化为作业(job)
➢ 在 Executor 之间调度任务(task)
➢ 跟踪 Executor 的执行情况
➢ 通过 UI 展示查询运行情况
4.2 Executor
Spark Executor 是集群中工作节点(Worker)中的一个 JVM 进程,负责在 Spark 作业 中运行具体任务(Task),任务彼此之间相互独立。
Spark 应用启动时,Executor 节点被同时启动,并且始终伴随着整个 Spark 应用的生命周期而存在。
如果有 Executor 节点发生了故障或崩溃,Spark 应用也可以继续执行,会将出错节点上的任务调度到其他 Executor 节点 上继续运行。
Executor 有两个核心功能:
➢ 负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程
➢ 它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存式存储。RDD 是直接缓存在 Executor 进程内的,因此任务可以在运行时充分利用缓存数据加速运算。
5 Spark核心编程
Spark计算框架为了能够进行高并发和高吞吐的数据处理,封装了三大数据结构,用于处理不同的应用场景。三大数据结构分别是:
RDD:弹性分布式数据集
累加器:分布式共享只写变量
广播变量:分布式共享只读变量
分布式计算模拟------利用网络编程
Driver------->client
Executor—>server
Task---------->计算数据和逻辑,对应下边的RDD
5.1 RDD:弹性分布式数据集
RDD---------------(Resilient Distributed Dataset)
RDD叫做弹性分布式数据集,是Spark中最基本的数据处理模型。代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。
RDD封装了计算逻辑,并不保存数据
RDD是一个抽象类,需要子类具体实现
RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑
可分区,并行计算
IO基本原理
IO操作体现了装饰者设计模式 延迟加载
1)RDD和IO之间的关系,将RDD类比于IO
2)核心属性:
①分区列表:用于执行任务时并行计算,是实现分布式计算的重要属性
②分区计算函数:Spark 在计算时,是使用分区函数对每一个分区进行计算
③依赖关系:RDD 是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个 RDD 建立依赖关系
④分区器(可选):当数据为 KV 类型数据时,可以通过设定分区器自定义数据的分区
⑤首选位置(可选):计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算
3)执行原理
RDD 是 Spark 框架中用于数据处理的核心模型
RDD 在整个流程中主要用于将逻辑进行封装,并生成 Task 发送给 Executor 节点执行计算
4)基础编程
创建 从集合(内存)中创建和从RDD中创建
//textFile:以行为单位来读取数据,读取的数据是字符串
//wholeTextFiles:以文件为单位读取数据
//读取的结果表示为元组,第一个元素表示文件路径,第二个元素表示文件内容
RDD并行度与分区
这里并行执行的任务数量并不是指的切分任务的数量
local [*]表示默认处理器的核数
分区数量问题
分区数据的分配
读取内存数据时,数据可以按照并行度的设定进行数据的分区操作
读取文件数据时,数据是按照 Hadoop 文件读取的规则进行切片分区,而切片规则和数据读取的规则有些差异
5.1.1 RDD转换算子
Scala有两种变量,val和var。
val就不能再赋值了。与之对应的,var可以在它生命周期中被多次赋值。
RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value 类型
Value类型
①map
Scala自减原则
rdd的计算一个分区内的数据是一个一个执行逻辑只有前面一个数据全部的逻辑执行完毕后,才会执行下一个数据。分区内数据的执行是有序的。不同分区之间的执行是无序的 .
②mapPartitions
不适用于内存小,数据量大的情况
与map的对比:
》》数据处理角度
Map算子是分区内一个数据一个数据的执行,类似于串行操作。而mapPartitions算子是以分区为单位进行批处理操作
》》功能角度
Map算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。
MapPartitions算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,所以可以增加或减少数据
③mapPartitionswithIndex
④flatMap
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射。
⑤glom
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
⑥groupBy
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为 shuffle。
⑦filter
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现 数据倾斜。
⑧sample
根据制定的规则
⑨distinct
将数据集中的重复的数据去重
⑩coalesce
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本
11)repartition
该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。无论是将分区数多的 RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition 操作都可以完成,因为无论如何都会经 shuffle 过程。
12)sortBy
该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理 的结果进行排序,默认为升序排列。排序后新产生的 RDD 的分区数与原 RDD 的分区数一致。中间存在 shuffle 的过程
双Value类型
①intersection
对源 RDD 和参数 RDD 求交集后返回一个新的 RDD
②union
对源 RDD 和参数 RDD 求并集后返回一个新的 RDD
③substract
以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来。
④zip
将两个 RDD 中的元素,以键值对的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD 中的元素,Value 为第 2 个 RDD 中的相同位置的元素。
问题:
如果两个RDD数据类型不一致怎么办?
如果两个RDD数据分区不一致怎么办?
如果两个RDD分区数据数量不一致怎么办?
⑤partitionBy
将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitioner
⑥reduceByKey
将数据按照相同的 Key 对 Value 进行聚合
⑦groupByKey
将数据源的数据根据 key 对 value 进行分组
与reduceByKey的区别:
从 shuffle 的角度:reduceByKey 和 groupByKey 都存在 shuffle 的操作,但是 reduceByKey 可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的数据量,而 groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较高。
从功能的角度:reduceByKey 其实包含分组和聚合的功能。GroupByKey 只能分组,不能聚合,所以在分组聚合的场合下,推荐使用 reduceByKey,如果仅仅是分组而不需要聚合。那 么还是只能使用 groupByKey
⑧aggregateByKey
将数据根据不同的规则进行分区内计算和分区间计算
eg:
取出每个分区内相同 key 的最大值然后分区间相加
⑨foldByKey
当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey
⑩combineByKey
11)sortByKey
在一个(K,V)的 RDD 上调用,K 必须实现 Ordered 接口(特质),返回一个按照 key 进行排序的
12)join
在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素连接在一起的 (K,(V,W))的 RDD
13)leftOuterJoin
14)cogroup
在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类型的 RDD
5.1.2 RDD行动算子
①reduce
聚集 RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据
②collect
在驱动程序中,以数组 Array 的形式返回数据集的所有元素
③count
返回 RDD 中元素的个数
④first
返回 RDD 中的第一个元素
⑤take
返回一个由 RDD 的前 n 个元素组成的数组
⑥takeOrdered
返回该 RDD 排序后的前 n 个元素组成的数组
⑦aggregate
分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合
⑧fold
⑨countByKey
⑩save相关算子
11)foreach
分布式遍历 RDD 中的每一个元素,调用指定函数
5.1.3 RDD序列化
对象User创建是在Driver中的,而算子执行是在Executor中,因此需要将User进行网络上的传输,所以需要进行序列化
样例类:
样例类在编译时,会自动混入序列化特质(可实现可序列化接口)
闭包检测
在 scala 的函数式编程中,就会导致算子内经常会用到算子外的数据,这样就 形成了闭包的效果,如果使用的算子外的数据无法序列化,就意味着无法传值给 Executor 端执行,就会发生错误,所以需要在执行任务计算前,检测闭包内的对象是否可以进行序列化,这个操作我们称之为闭包检测。
实际中遇到的问题需注意分析--------类的生命周期
Kyro序列化框架
Java 的序列化能够序列化任何的类。但是比较重(字节多),序列化后,对象的提交也比较大。Spark 出于性能的考虑,Spark2.0 开始支持另外一种 Kryo 序列化机制。**Kryo 速度是Serializable 的 10 倍。**当 RDD 在 Shuffle 数据的时候,简单数据类型、数组和字符串类型 已经在Spark 内部使用 Kryo 来序列化。
注:
即使使用 Kryo 序列化,也要继承 Serializable 接口。
关键字transient是指不能序列化
5.1.4 RDD依赖关系
依赖
血缘
RDD不存储数据,需要将RDD之间的关系保存下来,一旦出现错误,可以根据血缘关系重新进行计算
RDD 的 Lineage(血统) 会记录 RDD 的元数据信息和转换行为,当该 RDD 的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。
依赖关系----宽窄依赖
OneToOne(窄)依赖
shffule(宽)依赖
RDD阶段划分(看源码)
RDD任务划分(看源码)
RDD任务切分中间分为:Application、Job、Stage和Task
Application:初始化一个 SparkContext 即生成一个 Application;
Job:一个 Action 算子就会生成一个 Job;
Stage:Stage 等于宽依赖(ShuffleDependency)的个数加 1;
Task:一个 Stage 阶段中,最后一个 RDD 的分区个数就是 Task 的个数。
底层实现:
5.1.5 RDD持久化
因为RDD是不存储数据的,所以如果要完成多个算子功能的话,将重复很多流程,引出持久化概念。
存储级别
检查点
所谓的检查点其实就是通过将 RDD 中间结果写入磁盘 由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点 之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。 对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发。
缓存和检查点区别
1)Cache 缓存只是将数据保存起来,不切断血缘依赖。Checkpoint 检查点切断血缘依赖。
2)Cache 缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint 的数据通常存 储在 HDFS 等容错、高可用的文件系统,可靠性高。
3)建议对 checkpoint()的 RDD 使用 Cache 缓存,这样 checkpoint 的 job 只需从 Cache 缓存中读取数据即可,否则需要再从头计算一次 RDD
5.1.6 RDD分区器
Spark 目前支持 Hash 分区和 Range 分区,和用户自定义分区。Hash 分区为当前的默认分区。分区器直接决定了 RDD 中分区的个数、RDD 中每条数据经过 Shuffle 后进入哪个分区,进而决定了 Reduce 的个数。
➢ 只有 Key-Value 类型的 RDD 才有分区器,非 Key-Value 类型的 RDD 分区的值是 None
➢ 每个 RDD 的分区 ID 范围:0 ~ (numPartitions - 1),决定这个值是属于那个分区的。
5.1.7 RDD文件读取与缓存
Spark 的数据读取及数据保存可以从两个维度来作区分:文件格式以及文件系统。
文件格式分为:txt 文件、csv 文件、sequence 文件以及 Object 文件;
文件系统分为:本地文件系统、HDFS、HBASE 以及数据库。
5.2 累加器:分布式共享只写变量
累加器用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量,在 Executor 端的每个 Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后, 传回 Driver 端进行 merge。
5.3 广播变量:分布式共享只读变量
广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个 Spark 操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是 Spark 会为每个任务分别发送。