3、Spark2x 基于内存的计算引擎

版权声明: https://blog.csdn.net/KamRoseLee/article/details/83900495

Spark2x 基于内存的计算引擎

一、Spark 概述

Spark 是一种基于内存进行计算的分布式批处理引擎,他的主要工作是执行以下几种计算:

(1) 数据处理,可以进行快速的数据计算工作,具备容错性和可拓展性。

(2) 迭代计算,Spark 支持迭代计算,可以对多步数据逻辑处理进行计算工作。(3) 数据挖掘,在海量数据基础上进行挖掘分析,可以支持多种数据挖掘和机器学习算法。

对比于 hadoop 来说,Spark 更适用于数据处理,机器学习,交互式分析,最主要的是迭代计算中,它可以提供比 hadoop 更低的延迟,更高效的处理,并且开发效率更高,容错性也更好,但是需要注意的是,Spark 的性能只有在进行多层迭代计算的时候才会有显著的性能提升,相对于迭代层数少的现象,Spark 的计算性能提升的并不明显,基本和 Hadoop 持平。

Spark 是基于内存的分布式批处理引擎,它最大的特点是延迟小,具有很高的容错性和可拓展性,它和其他引擎的最大的区别在于,它支持进行迭代计算, Spark 主要适用的场景在低延迟的迭代计算中,它和传统的数据处理引擎最大的不同,在于 Spark 会将计算中的临时文件或者临时数据存放在内存中,这样在进行反复的引用时,就不需要再从磁盘中进行数据读取,而是选择更快的内存进行该操作。那么相比于传统 Hadoop 架构,Spark 理论速度会高于 Hadoop100 倍以上,但是,这个参数是有条件的,在迭代的层级较少的时候,这个差距并不明显,还有可能 Spark 的计算速度没有 hadoop 快,但是当反复的重复引用和迭代层数多以后,这个差距就会越来越明显。

(1) SparkCore:类似于 MR 的分布式内存计算框架,Core 是整体 Spark 计算的核心进程,上层的计算按照计算的类型进行区分,用于适应不同层面的和不同目的的计算,然后计算的流程和中间处理进程都是由 Core 来进行的计算,所以

本身从原理上来说,Spark 通过各种上层进程,满足了不同需求的计算,并且进行转化,下层的 Core 只进行计算,这样做就可以保证整体的所有组件都正常运行,并且达到资源的利用最大化,最大的特点是将中间计算结果直接放在内存中,提升计算性能。自带了 Standalone 模式的资源管理框架,同时,也支持 YARN、 MESOS 的资源管理系统。FI 集成的是 SparkOnYarn 的模式。其它模式暂不支持。(2) SparkSQL:SparkSQL 是一个用于处理结构化数据的 Spark 组件,作为

Apache    Spark 大数据框架的一部分,主要用于结构化数据处理和对数据执行类SQL 查询。通过 SparkSQL,可以针对不同数据格式(如:JSON,Parquet,ORC 等)

和数据源执行 ETL 操作(如:HDFS、数据库等),完成特定的查询操作。当我们需要对 Sql 进行相关的操作的时候,就像之前所说,首先用户通过 SQL 语句发送对应的请求到达 Spark 的 SQL 执行引擎,SQL 执行引擎将我们提交的请求做语义执行,转化为对应的对数据的操作,之后,将对应的操作和执行处理任务交给 Core 来进行计算。

(3) SparkStreaming :微批处理的流处理引擎,将流数据分片以后用 SparkCore 的计算引擎中进行处理。相对于 Storm,实时性稍差,优势体现在吞吐量上。

(4)     Mllib 和 GraphX 主要一些算法库。

(5)     FusionInsight Spark 默认运行在 YARN 集群之上。

(6)     Structured    Streaming 为 2.0 版本之后的 spark 独有。它是构建在

SparkSQL 上的计算引擎,其将流式数据理解成为是不断增加的数据库表,这种流式的数据处理模型类似于数据块处理模型,可以把静态数据库表的一些查询操作应用在流式计算中,Spark 执行标准的 SQL 查询,从无边界表中获取数据。

三、SparkCore 技术原理

1.Spark 的流程角色和基本概念

角色基本概念:

(1)Client:用户方,负责提交请求 Client 和之前的概念一样,是一个引擎内的进程,提供了对外访问的接口和对内组件进程的交互。

(2)Driver:负责应用的业务逻辑和运行规划(DAG)

Driver 是一个新的组件,和之前的处理引擎最大的不同在于,我们处理 Spark 的数据时,由于 Spark 是一个基于内存的处理引擎,所以其数据计算时有一个很典型的特点就是迭代化的计算处理,所以我们在具体将任务切分的时候,就不能像之前的处理引擎一样,只是单纯的将任务做切分下发,因为任务和任务之间这次存在了强耦合或者是弱耦合的关联性关系,而计算的结果又会出现相互的调用,也就是结果之间存在有依赖关系,所以我们在处理 Spark 的计算时,就必须对任务的执行过程做一个非常详细的控制,什么时候需要做什么计算,需要调用什么样的结果,这都是 Driver 需要进行规划和控制的,所以在我们执行相关的任务之前,Driver 就需要对任务做相关的划分和处理的顺序的控制,将任务的执行规划整合为 DAG 运行规划图来进行下发(DAG:DAG 是一个应用被切分为任务之后的执行的相关处理流程,主要是用来控制任务的执行顺序和调用的数据,DAG

是一个有向无环图,也就是说,任务执行是有相关的执行顺序的,这就保证了有向,但是任务同时又是一定会执行完毕的,所以无环就保证了任务一定会有终止。)(3)ApplicationMaster:负责应用的资源管理,根据应用需要,向资源管理部门(ResourceManager)申请资源。

这里说的 AppMaster 和 MR 的有一定的不同,区别如下:

①MR 的 AppMaster 需要负责的功能很多,但是 Spark 的 AppMaster 功能较单一

②MR 的 AppMaster 在创建完成之后需要和 RM 模块进行注册操作,Spark 的不需要

在向 RM 申请资源之后,MR 的 AppMaster 需要自己和 NM 通信要求其拉起

Container,Spark 的不需要,Executor 是由 RM 直接下发创建的。 ④Container 和 Executor 创建完成之后,如果是 MR 的 AppMaster,则会下发 task 到 Container 中,如果是 Spark,则由 driver 下发 task。

如果用户需要查询相关的处理进度信息,MR 的 AppMaster 负责返回执行进度,如果是 Spark,则由 Driver 执行。

执行完成之后 MR 的 AppMaster 需要注销,Spark 的 AppMaster 不需要注销。

如果执行过程中出现故障,那么 MR 的 AppMaster 负责重新下发 task 执行,在

Spark 中这个工作由 Driver 做。

所以综上所述,MR 和 Spark 的 AppMaster 最大的区别在于,MR 的 AppMaster 是

一个综合管理的进程,但是 Spark 中,这个工作就由 Driver 来做,AppMaster 只负责资源的计算和申请操作。Driver 是一个固定进程,MR 的 AppMaster 是一个临时进程。

(4)ResourceManager:资源管理部门,负责整个集群资源统一调度和分配。 RM 是 Yarn 中的组件,和 MR 和 Spark 没关系,这里两个引擎只是调用了 Yarn 中的 RM,没有在自身引擎内创建和维护 RM。

(5)Executor:负责实际计算工作,一个应用会分拆给多个 Executor 来进行计算。

Executor 是一个更小化的概念,我们的 AppMaster 在申请的时候,申请的是 Container 容器,然后 RM 将对应的资源做封装,然后和 NM 进行通信,要求 NM 启动 Container,在 Container 中,我们又将资源做了进一步的切分,最终形成一个个的 Executor,所以这里所说的 Executor 是属于 Container 的,我们在下发任务时,可以给一个 Executor 中下发多个 task 进行执行。而 Executor 之间的计算又是分布式的。

流程基本概念:

(1)Application:Spark 用户程序,提交一次应用为一个 Application,一个 App 会启动一个 SparkContext,也就是 Application 的 Driver,驱动整个 Application 的运行。

我们所做的一个任务就是指提交一个 Application,一个应用中包含的就是对数据所需要执行的计算,以及对计算相关的控制操作,除了这两部分之外,我们在 Application 中还封装了相关的执行所需要的一些进程驱动,比如对 Driver 和 AppMaster 的相关配置和处理文件。那么在 Application 中我们提交的计算就是一个整体的计算进程。

(2)Job:一个 Application 可能包含多个 Job,每个 action 算子对应一个 Job; action 算子有 collect,count 等。

Driver 将一个 Application 中提交的计算进程,切分为执行的几个计算,比如说用户下发一个做菜的要求,那么 Driver 就会将该请求解析切分为洗菜、切菜、炒菜的一步步的 job 来进行执行。

(3)Stage:每个 Job 可能包含多层 Stage,划分标记为 shuffle 过程;Stage按照依赖关系依次执行。

那么在每一个 job 子任务中,我们又需要做很多的计算工作,这个时候,我们会检查当前需要执行的计算中的数据之间的关联性和相关的执行的耦合度,然后根据数据以及结果之间的依赖关系,将其分为宽依赖和窄依赖,我们从 Driver 一开始分析出的 DAG 图中,从最末端开始向总结果的方向去执行,一旦在 DAG 中发现出现宽依赖,则将其拆分为窄依赖来进行计算工作,重新规划 DAG 图,并认为凡是从开始执行直到遇到宽依赖的中间计算流程,都属于是一个计算阶段。当所有的 job 中不在存在有宽依赖时,我们的计算规划就保证已经处于是一个最小化计算的,最小化迭代的这么一个状态了。那么我们这个时候就可以将一层一层的迭代计算中所需要执行的每一步操作都封装为一个 task 然后下发执行了,这个时候整体的计算流程就已经划分为了 Application—Job—Stage—task

(4)Task:具体执行任务的基本单位,被发到 executor 上执行。

Task 是整体计算中的最小单位,只有 task 的计算是不存在迭代关系的,但是 task 在执行的时候可能会依赖于某一些其他 task 的计算结果。

2.Spark 的应用运行流程

(1)Client 向 Driver 发起应用计算请求,将 Driver 启动,并且同时申请 JobID保证全局应用的唯一合法性。

首先,用户通过相关的接口连接到 Client,然后提交 Application 应用到 Client, Client 将请求转发到 Spark,启动 Driver,Driver 会根据 Application 中的数据和信息,对任务进行相关的计算工作,然后把 Application 中的计算请求解析,并且拆解,最终成为 DAG 的形式提交,按照如上的基本概念(2)Driver 向 RM 申请 AM,来计算本次的应用数据

Driver 在执行完如上的计算之后,会根据用户提交的 Application 中关于 AppMaster 的控制文件和相关信息,计算其所需要使用资源,然后将请求发送给RM,请求创建一个 Container,并在其中拉起 AppMaster。

(3)RM 在合适的设备节点上启动 AM,来进行应用的调配和控制 RM 在收到请求之后,首先会发送相关的请求到各个 NM,检查每个 NM 的负载情

况,并且选择当前负载最小的 NM 节点进行通信,下发相关的请求给 NM,要求其在自身封装对应的资源,拉起一个 Container,并在 Container 创建完成之后在其中打开 AppMaster。

(4)AM 向 RM 申请容器进行数据的计算 AppMaster 拉起之后,会根据 Driver 中记录 DAG 计算当前执行任务所需要消耗

的资源,然后根据计算的结果向 RM 发起资源的申请请求,这里的 AppMaster 和 RM 中的相同,会按照轮询式的请求方法,计算每一步操作所需要消耗的资源,然后逐个下发申请,而不是根据计算所需要消耗的资源总量去进行一次性申请。

(5)RM 选择合适的节点进行容器的下发,并且在容器上启动 Executor RM 按照之前的方式,根据负载在 NM 上拉起 Container,并且要求 Container 在

创建完成之后在内部继续创建 Executor。

(6)Executor 创建完成之后,会向 Driver 进行注册。保证其合法性。

(7)Driver 会将任务按照 DAG 的执行规划,一步步的将 Stage 下发到 Executor上进行计算

计算的过程中,数据会根据调用的依赖关系,先缓存在内存中,如果内存中的容量不足,我们也需要根据时间戳将最先写入到内存中的部分数据下盘。计算全部结束之后,Executor 就会关闭自身进程,然后 NM 将资源做回收操作。

(8)任务计算完成之后,Driver 会向 RM 进程发送注销信息,完成应用的计算,并且向 Client 返回对应的执行结果。

如果在执行的过程中,我们有相关的查询操作,这个时候,请求会通过 Client 下发给 Driver 进行查询,如果 Driver 查询到某一个 task 执行卡住,或者执行的速度过慢,这个时候就会选择一个 Executor 下发一个相同的任务,那个任务先执行完,就使用哪一个任务的结果,这样就可以保证在整体执行的时候不会由于某一个进程的执行速度过慢,而导致的整体计算被卡住。

3.RDD 弹性分布数据集

由于 Driver 整体是将数据切分成了阶段去进行执行,那么 DAG 本身就是一个关于计算执行的控制流程。DAG 主要是用于控制计算的顺序和计算的结果调用的规划。那么在实际进行计算的时候,一个大的数据也就会按照这种 DAG 被切分成了很多小的数据集被反复调用或者根据依赖关系缓存。这个时候,这种由阶段性计算产生的小的数据集就被称为 RDD 弹性分布式数据集。

弹性分布数据集,指的是一个只读的,可分区的分布式数据集。这个数据集的全部或部分可以缓存在内存,在多次计算之间重用。

(1)RDD 的生成:从 Hadoop 文件系统(或与 Hadoop 兼容的其它存储系统)输入创建(如 HDFS)。或者从父 RDD 转换得到新的 RDD。

在实际的操作中,我们提交业务的时候是按照整体文件进行的提交,提交进来之后,我们首先在 Duiver 中会进行 Application 到 task 的 DAG 切分,这个时候计算的数据,就由原先的整体文件被切分为了一个个的 RDD,我们计算的时候是按照 DAG 的执行反向顺序进行的创建,由一个大的文件,也就是量级比较大的父 RDD,逐步的切分为多个子 RDD,直到最终子 RDD 的对应关系可以和 task 进行匹配。

(2)RDD 的存储和分区:用户可以选择不同的存储级别存储 RDD 以便重用(11种)。当前 RDD 默认存储于内存,但当内存不足时,RDD 会溢出到磁盘中。RDD在需要进行分区时会根据每条记录 Key 进行分区,以此保证两个数据集能高效进行 Join 操作。

我们在执行相关的计算时,主要有两种大类的操作,为 transfermation 和action, 其中 transfermation 执行的操作结果还是一个 RDD,相当于在执行计算的过程中,transfermation 产生的是临时的数据集,对数据做一些基本的操作和处理。如 map 、filter 、join 等。Transformation 都是 Lazy 的,代码调用到 Transformation 的时候,并不会马上执行,需要等到有 Action 操作的时候才会启动真正的计算过程。 action 产生的是结果,如 count,collect,save 等,Action 操作是返回结果或者将结果写入存储的操作。Action 是 Spark 应用真正执行的触发动作。

RDD 的 11 种存储级别:

(3)RDD 的优点:RDD 是只读的,可提供更高容错能力。RDD 的不可变性,可以

实现 HadoopMapReduce 的推测式执行。RDD 的数据分区特性,可以通过的本地性来提高能。RDD 都是可序列化的,在内存不足时自动降级为磁盘存储。

HadoopMapReduce 推测执行(SpeculativeExecution)(SpeculativeExecution):

是指在分布式集群环境下,因为负载不均衡或者资源分布等原造成同一个 job 的个别的个别 task 运行速度明显慢于其他 task,最终延长整个 Job 的执行时间。为了避免这种情况发生,Hadoop 会为该 task 启动备份任务,让该 speculative task 与原始 task 同时处理一份数据,哪个先运行完则将谁的结果作为最终结果,以提高整 Job 的执行时间。

RDD 的特点:失败自动重建。可以控制存储级别(内存,磁盘等)来进行重用。是静态类型的。

4.RDD 的宽依赖和窄依赖

RDD 父子依赖关系:

在 RDD 中,我们可以根据某个 RDD 进行操作(计算或者转换等)得到一个新的RDD,那么这个 RDD 在执行 Application 类操作的时候是会产生对原 RDD 的依赖关系,那么此时,原 RDD 成为父 RDD,新的 RDD 为子 RDD。

窄依赖(Narrow)指父 RDD 的每一个分区最多被一个子 RDD 的分区所用。

宽依赖(Wide)指子 RDD 的分区依赖于父 RDD 的所有分区,是 Stage 划分的依据。

(1)窄依赖对优化很有利。逻辑上,每个 RDD 的算子都是一个 fork/join(此 join 非 join 算子,而是指同步多个并行任务的 barrier);把计算 fork 到每个分区,算完后 join,然后下一个 fork/join。如果直接转换到物理实现,是很不经济的:一是每一个 RDD(即使是中间结果)都需要物化到内存或存储中,费时费空间;二是 join 作为全局的 barrier,是很昂贵的,会被最慢的那个节点拖死。如果子 RDD 的分区到父 RDD 的分区是窄依赖,就可以把两个 fork/join 合为一个;如果连续的变换算子序列都是窄依赖,就可以把很多个 fork/join 并为一个,不但减少了大量的全局 barrier,而且无需物化很多中间结果 RDD,这将极大地提升性能。Spark 把这个叫做 pipeline 优化。

RDD 的序列化,我们在进行计算时,由于 RDD 之间包括我们执行的计算之间都是有依赖关系的,那么在进行实际的计算时,也会存在计算的顺序。那么,当内存的容量不足时,我们就需要将一部分的 RDD 存储在硬盘中,这个时候,由于 RDD 存在序列化编号,这样我们就可以根据编号继续按顺序进行计算操作,而不会导致由于 RDD 没有编号产生的计算混乱的问题。

(2)窄依赖的优势:首先,narrow dependencies 可以支持在同一个 cluster node 上以管道形式执行多条命令,例如在执行了 map 后,紧接着执行 filter;其次, 则是从失败恢复的角度考虑。narrow dependencies 的失败恢复更有效,因为它只需要重新计算丢失的 parent partition 即可,而且可以并行地在不同节点进行重计算。

(3)Stage 划分:stage 的划分是 Spark 作业调度的关键一步,它基于 DAG 确定依赖关系,借此来划分 stage,将依赖链断开,每个 stage 内部可以并行运行, 整个作业按照 stage 顺序依次执行,最终完成整个 Job。实际应用提交的 Job 中 RDD 依赖关系是十分复杂的,依据这些依赖关系来划分 stage 自然是十分困难的, Spark 此时就利用了前文提到的依赖关系,调度器从 DAG 图末端出发,逆向遍

历整个依赖关系链,遇到 ShuffleDependency(宽依赖关系的一种叫法)就断开,遇到 NarrowDependency 就将其加入到当前 stage。stage 中 task 数目由 stage 末端的 RDD 分区个数来决定,RDD 转换是基于分区的一种粗粒度计算,一个 stage 执行的结果就是这几个分区构成的 RDD。

整体来说,由于本身我们在提交计算的时候,计算之间就存在有相关的依赖关系,各个计算结果相互进行迭代和调用,这也就导致了在转化为 DAG 的过程中,我们的数据与数据之间,包括计算的临时结果之间也是存在有相关的调用关系的,这样的话,某一些计算的 RDD 算子,就会依赖于其之前计算的 RDD,产生了相关的依赖关系,如果某个 RDD 只依赖于一个 RDD 的运算就可以执行自身的计算,那么我们称之为叫做窄依赖,如果某一个 RDD 需要多个 RDD 反馈的结果才能够满足执行下一步执行的条件,这个时候我们就称之为 RDD 之间的关系叫做宽依赖。在具体的实际执行过程中窄依赖要远远优秀于宽依赖,所以我们需要将宽依赖拆分为窄依赖,这样就可以提升整体的执行效率,那么每遇到一个宽依赖之后,我们将其拆分为窄依赖,然后就可以称之为是一个新的阶段,也就是 stage。如图所示:

 

在上图中,我们可以看到计算流程中一共由 4 个 RDD,RDD2/3/4 中有两个分区,所以我们可以发现 RDD2 和 RDD3 之间是具有宽依赖的,那么我们就需要将宽依赖拆分为窄依赖,如下图所示:

我们可以发现,在这张图中,我们其实本质上并没有拆分原本的宽依赖,而是将执行的阶段做了一个划分,从原理性的角度上来说,依赖问题其实解决的主要就是内存的占用问题,作为 Spark 来说,它利用内存作为数据的临时缓存空间的计算方式速度的确很快,但是同时它也造成了对内存占用过大的情况,所以就可以出现这种情况,当宽依赖出现的时候,由于宽依赖的子 RDD 需要依赖的父 RDD 中的所有分区,所以一旦有一个分区数据的计算卡住就会导致整个 RDD 计算被卡住,但是父 RDD 中计算出的分区的数据结果是不会等待所有的分区都计算完成才会加入到子 RDD 的,这就导致每当父 RDD 算出一个分区数据就会被马上载入到子 RDD 的所有分区。这样的话,本身计算由于父 RDD 中的某个分区卡住无法继续进行,但是已经计算出的数据却被复制多份加载到了子 RDD 的所有分区,这样做的话对于内存占用过高,所以我们需要避免这种情况的产生,这个时候我们就通过拆依赖的方式来实现,当拆依赖之后,我们必须等到某个阶段执行完成之后才能执行下一阶段,如上图中,我们必须要等待 Stage1 执行完毕,所有的分区数据都计算完成才能执行 Stage2,数据也才能被加载到下一个 Stage 中,这样的话,我们就节约了内存,减小了占用率。这就是拆依赖的原理。

RDD    Objects 产生 DAG,然后进入了 DAGScheduler 阶段,DAGScheduler 是面向

stage 的高层次的调度器,DAGScheduler 把 DAG 拆分成很多的 tasks,每组的 tasks 都是一个 stage,每当遇到 shuffle 就会产生新的 stage,DAGScheduler

需要记录那些 RDD 被存入磁盘等物化动作,同时需寻找 task 的最优化调度,例如数据本地性等;DAGScheduler 还要监视因为 shuffle 输出导致的失败;DAGScheduler 划分 stage 后以 TaskSet 为单位把任务交给底层次的可插拔的调度器 TaskScheduler 来处理;一个 TaskScheduler 只为一个 SparkContext 实例服务,TaskScheduler 接受来自 DAGScheduler 发送过来的分组的任务, DAGScheduler 给 TaskScheduler 发送任务的时候是以 Stage 为单位来提交的, TaskScheduler 收到任务后负责把任务分发到集群中 Worker 的 Executor 中去运行,如果某个 task 运行失败,TaskScheduler 要负责重试;另外如果 TaskScheduler 发现某个 Task 一直未运行完,就可能启动同样的任务运行同一个 Task,哪个任务先运行完就用哪个任务的结果。

Spark 的应用调度全部都是由 Driver 来完成的,当一个请求从 Client 发送到 Driver 中时,Driver 就开始执行相关的调度执行 1.Driver 会根据用户提交的请求创建 DAG。Driver 主要分为两个核心进程,一个是 DAG 调度器,主要用来对提交的业务来进行相关的规划,需要控制对应的 RDD 的依赖关系并且根据该 RDD 的依赖关系做任务的切分,也就相当于是将 Application 切分为 Stage 的过程,切分完成之后,对应的执行控制是由 task 控制器来做的,它需要下发任务,监控任务,并且在任务出现问题的时候重启或者是重新下发任务。所以 DAG 调度器做的是计算的逻辑控制,task 调度器做的是具体的执行控制。

2.创建完成之后就开始进行调度阶段,DAG 的调度器会将 DAG 切分为 task,其实也就相当于将 Application 切分为 task。task 是以组的形式存在的,一个组其实就是一个 stage,stage 中究竟有多少个 task 取决于 stage 中有多少个子 RDD,一个 RDD 对应的就是一个 task。

3.DAG 从 Application 的层级开始逐层向下进行相关的检查操作,每遇到一个宽依赖就将其切分为窄依赖,然后将对应层级的 RDD 做切分,然后形成一个 stage。4.切分完成之后,DAG 就需要安排具体的执行的顺序和操作,其实这里也就相当于根据 RDD 之间的依赖关系开始做执行的安排。DAG 需要通过相关的规划执行,保障整体执行的最优化。效率达到最高。

5.DAG 的调度器将任务安排好之后,就会将对应的 stage 分组任务交给 task 调度器去执行,task 调度器收到的执行要求是以 stage 为单位的,里面会根据 RDD 的个数产生对应多个的 task。

6.task 调度器会将 Stage 中的 task 下发给 worker 中的 Executor 来执行,其实也就相当于是 task 是下发给 container 中的 executor 来执行的。

7.task 调度器会实时的对计算的进度进行监控,当一个计算产生延迟并且长时无法返回对应的结果时,其会选择一个其他的 Executor 拉起该计算,两个进程谁先执行完,就使用谁的结果

四、SparkSQL 技术原理

SparkSQL 是 Spark 中基于 SparkCore 的一个计算工具,其将用户提交的SQL 语句解析成为 RDD,然后交由 SparkCore 执行,这样做我们就可以在 Spark

中无缝对接 SQL 的语句查询,执行相关的任务。在 SparkSQL 中,我们使用的数据资源叫做 DataSet 和 DataFrame。具体的解释下文中将会详细阐述。

DataSet 是一个由特定域的对象组成的强类型集合,可通过功能或关系操作并行转换其中的对象。Dataset 是一个新的数据类型。Dataset 与 RDD 高度类似,性能比较好。DataSet 以 Catalyst 逻辑执行计划表示,并且数据以编码的二进制形式存储,不需要反序列化就可以执行 sort、filter、shuffle 等操作。Dataset 与 RDD 相似, 然而, 并不是使用 Java 序列化或者 Kryo 编码器来序列化用于处理或者通过网络进行传输的对象。 虽然编码器和标准的序列化都负责将一个对象序列化成字节,编码器是动态生成的代码,并且使用了一种允许 Spark 去执行许多像 filtering, sorting 以及 hashing 这样的操作,

不需要将字节反序列化成对象的格式。

jvm 中存储的 java 对象可以是序列化的,也可以是反序列化的。序列化的对象是将对象格式化成二进制流,可以节省内存。反序列化则与序列化相对,是没有进行二进制格式化,正常存储在 jvm 中的一般对象。RDD 可以将序列化的二进制流存储在 jvm 中,也可以是反序列化的对象存储在 JVM 中。至于现实使用中是使用哪种方式,则需要视情况而定。例如如果是需要最终存储到磁盘的,就必须用序列化的对象。如果是中间计算的结果,后期还会继续使用这个结果,一般都是用反序列化的对象。

本质上来说,RDD 和 DataSet 的区别并不大,他们都属于是特殊的数据形式,我们讲计算引擎在进行计算的时候,必须需要两个东西,一个是数据,一个是对数据的计算方法,也就是我们需要应用和数据。数据是信息载体,应用是对数据的操作手段和计算方法。

由于 Spark 是在内存中进行计算的一种引擎,所以为了避免对内存的过度占用,我们一般会将数据进行序列化操作,也就是将原本正常的数据转换成为二进制表示来进行存储,这样做的好处就是节省内存,而 SparkCore 可以将源数据进行序列化操作,之后将产生的二进制数据直接封装到 RDD 中,当我们需要对其进行计算的时候,再把二进制数据转换为正常的数据,这样就是反序列化操作。Dataset 的优势就在于,其可以不进行反序列化,SparkSQL 可以直接识别出二进制数据的含义,这样节省了大量的计算延迟和转换开销。

DataFrame 提供了详细的结构信息,使得 Spark SQL 可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么。DataFrame 多了数据的结构信息,即 schema。这里主要对比 Dataset 和 DataFrame,因为 Dataset 和 DataFrame 拥有完全相同的成员函数,区别只是每一行的数据类型不同

DataFrame 也可以叫 Dataset[Row],每一行的类型是 Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用 getAS 方法或者共性中的模式匹配拿出特定字段。而 Dataset 中,每一行是什么类型是不一定的,在自定义了 case class 之后可以很自由的获得每一行的信息,结合上图总结出,DataFrame 列信息明确,行信息不明确。

由于 DataFrame 带有 schema 信息,因此,查询优化器可以进行有针对性的优化,以提高查询效率。

DataFrame 在序列化与反序列化时,只需对数据进行序列化,不需要对数据结构进行序列化。

Row:代表关系型操作符的输出行;类似 Mysql 的行。

由于本身 SparkSQL 就是对接 SQL 指令的下发执行的,所以我们在看DataSet 和 DataFrame 的时候可以按照 SQL 的表格类型来看数据。DataSet 可以对应到 Row 上去理解。而行数据本身只有内容,不包含数据结构,如果需要让行包含数据结构,就需要我们自己通过 class 的类定义来实现。DataFrame 是站在列结构的角度上来对数据进行划分的,这个时候 DataFrame 是自身包含列的结构信息的,所以对于 DataFrame 来说,自身是携带数据和属性的,DataSet自身只有数据,属性信息可以通过配置携带。

RDD:优点:类型安全,面向对象。缺点:RDD 无论是集群间的通信, 还是 IO 操作都需要对对象的结构和数据进行序列化和反序列化。序列化和反序列化的性能开销大;GC 的性能开销,频繁的创建和销毁对象, 势必会增加 GC。

DataFrame:优点:自带 scheme(设计)信息,降低序列化反序列化开销。

DataFrame 另一个优点,off-heap : 意味着 JVM 堆以外的内存, 这些内存直接受操作系统管理(而不是 JVM)。Spark 能够以二进制的形式序列化数据(不包括结构)到 off-heap 中, 当要操作数据时, 就直接操作 off-heap 内存. 由于 Spark 理解 schema, 所以知道该如何操作。缺点:不是面向对象的;编译期不安全。

Dataset 的特点:快:大多数场景下,性能优于 RDD;Encoders 优于 Kryo

或者 Java 序列化;避免不必要的格式转化。类型安全:类似于 RDD,函数尽可能编译时安全。和 DataFrame,RDD 互相转化。Dataset 具有 RDD 和 DataFrame的优点,又避免它们的缺点。

五、Spark Structured Streaming 技术原理

1.Spark Structured Streaming 概念

Structured Streaming 是构建在 Spark SQL 引擎上的流式数据处理引擎。可以像使用静态 RDD 数据那样编写流式计算过程。当流数据连续不断的产生

时,Spark SQL 将会增量的、持续不断的处理这些数据,并将结果更新到结果集中。

 

从此处开始,后边会有很多的组件涉及到流式数据处理。那么目前在大数据的组件中,有很大的一部分都涉及到了流式数据处理。那么根据时间的进程,首先出现的是批处理,之后出现了流处理。批处理主要是针对于大量的小文件进行处理的一种方式。而流处理主要是针对于大型文件的处理。我们可以做一个想象,比如今天家里停水了,打开水龙头的时候,水断断续续的流出,这种可以理解成为是批处理的数据情况,而没有停水的时候,打开水龙头,水流持续流出,这个时候就可以理解为流式数据。而流式数据本身也具有顺序性和不可篡改的特性。其也是由很多的小水滴构成的,所以流式数据还是可以被切分成数据块的。

 

批处理数据一般来说是针对于大量小文件的,对数据处理完成之后,比如一个批次的数据处理好,那么任务就结束了。而对于流处理数据来说,主要有两种组成方式,一种是少量的大文件,持续不断地输入构成流数据。另一种是业务持续存在,数据持续不断地从各个来源汇聚输入到引擎中构成数据流。目前在业务中,分析业务主要场景是流式数据的第一种。而互联网厂商,比如阿里、腾讯等企业更多的是使用后者。两种流式数据使用哪一种的本质区别就在于数据的来源,分析业务主要是用户自身拥有大量的数据。互联网厂商由于本身数据量产生较小,所以更多的是依托于后者,也就是底层用户产生数据,互联网厂商汇总进行分析构成流式数据。

 

Structured Streaming 的核心是将流式的数据看成一张数据不断增加的数据库表,这种流式的数据处理模型类似于数据块处理模型,可以把静态数据库表的一些查询操作应用在流式计算中,Spark 执行标准的 SQL 查询,从无边界表中获取数据。

无边界表:新数据不断到来,旧数据不断丢弃,实际上是一个连续不断的结构化数据流。

在这种流程中,已经计算完成的数据被不断地丢弃,新的数据持续的被加入到数据集的末尾。对于这个数据集来说,是一个无头无尾的数据集。但是这样也体现了流式数据的持续性。

Structured Streaming 的计算是按照时间顺序为方式来进行计算的。每一条查询的操作都会产生一个结果集 Result Table。每一个触发间隔,当新的数据新增到表中,都会最终更新 Result Table。无论何时结果集发生了更新,都能将变化的结果写入一个外部的存储系统。

Structured Streaming 在 OutPut 阶段可以定义不同的数据写入方式,有如下 3 种:

(1) Complete Mode:整个更新的结果集都会写入外部存储。整张表的写入操作将由外部存储系统的连接器完成。

(2) Append Mode:当时间间隔触发时,只有在 Result Table 中新增加的数据行会被写入外部存储。这种方式只适用于结果集中已经存在的内容不希望发生改变的情况下,如果已经存在的数据会被更新,不适合适用此种方式。

(3) Update Mode:当时间间隔触发时,只有在 Result Table 中被更新的数据才会被写入外部存储系统。注意,和 Complete Mode 方式的不同之处是不更新的结果集不会写入外部存储。

三种模式的具体操作如下图所示,每种操作模式分别对应每一个时间的输入和输出操作:

六、Spark Streaming 技术原理

Spark Streaming 计算基于 DStream,将流式计算分解成一系列短小的批处理作业。

Spark Streaming 本质仍是基于 RDD 计算,当 RDD 的某些 partition 丢失,可以通过 RDD 的血统机制重新恢复丢失的 RDD。

事实上,Spark Streaming 绝对谈不上比 Storm 优秀。这两个框架在实时计算领域中,都很优秀,只是擅长的细分场景并不相同。Spark Streaming 仅仅在吞吐量上比 Storm 要优秀。对于 Storm 来说:

1、建议在那种需要纯实时,不能忍受 1 秒以上延迟的场景下使用,比如实时金融系统,要求纯实时进行金融交易和分析。2、如果对于实时计算的功能中,要求可靠的事务机制和可靠性机制,即数据的处理完全精准,一条也不能多,一条也不能少,也可以考虑使用 Storm。3、如果还需要针对高峰低峰时间段,动态调整实时计算程序的并行度,以最大限度利用集群资源(通常是在小型公司,集群资源紧张的情况),也可以考虑用Storm。

4、如果一个大数据应用系统,它就是纯粹的实时计算,不需要在中间执行 SQL 交互式查询、复杂的 transformation 算子等,那么用 Storm 是比较好的选择。对于 Spark Streaming 来说:

1、如果对上述适用于 Storm 的三点,一条都不满足的实时场景,即,不要求纯实时,不要求强大可靠的事务机制,不要求动态调整并行度,那么可以考虑使用Spark Streaming。

2、位于 Spark 生态技术栈中,因此 Spark Streaming 可以和 Spark Core、Spark SQL 无缝整合,也就意味着,我们可以对实时处理出来的中间数据,立即在程序中无缝进行延迟批处理、交互式查询等操作。这个特点大大增强了 Spark Streaming 的优势和功能。

猜你喜欢

转载自blog.csdn.net/KamRoseLee/article/details/83900495