Spark核心之编程模型详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/flyinthesky111/article/details/82503544

  此次经验分享共分为两部分,上部分主要偏向理论介绍,下部分更偏向代码实操
此经验来源于《图解Spark核心技术与案例》一书,书挺不错的,有需要学习的可以去看看。

一、整体介绍

  最近几十年,随着计算机行业和互联网的发展,数据量也得到了爆发式的增长,以往的单台机器去处理数据的能力远远满足不了现在数据处理的性能要求。针对这种现状,谷歌提出了MR模型,后来又出现了storm流处理系统,impala交互式SQL查询系统以及BSP的秉性图计算模型。但是,这些框架在实现计算的时候,数据共享只能通过外部存储来实现,而这又会引入数据备份,磁盘I/O,序列化等等问题。spark和前面这些框架的解决办法思路相反,他设计了统一的编程抽象——弹性分布式数据集(RDD)。这种全新的模型可以使用户直接控制数据的共享,使得用户可以指定将数据存储到磁盘,内存。RDD的提出,不仅增加了高效的数据共享,还 大大增加了使用过程中的通用性。要想了解spark的核心,就必须要了解一下这个spark的编程模型——RDD。
  本次编程模型的分享准备从一下几个方面介绍:

1. RDD的简介以及类型划分。
2. spark任务调度中RDD的作业调度流程。
3. spark中 持久化存储RDD的额存储策略。
4. RDD的检查点支持。
5. RDD的编程接口。
6. RDD的创建。
7. RDD的转换操作。
8. RDD的控制操作。
9. RDD的行动操作。
其中,前四个主要是对spark任务中RDD的划分,调度,以及数据存储和灾备恢复的介绍。后五个则是具体的RDD的编程操作。

二、具体分享

  废话不多说,进入正题,咱们先来看一下第一个:
     RDD的简介以及类型划分。
  简介
  稍微接触过spark的人都知道,RDD是一个抽象,是一个弹性分布式数据集。相对于大数据的进化史来说,他是MR模型的一个进化,解决了MR模型的一些缺陷。相比较上面说到的一些数据处理的框架,spark框架将计算转化为一个有向无环图(DAG),这使得在计算过程中,能够有效的恢复DAG中的故障和满节点执行的任务,而且,RDD会提供一个粗粒度的数据操作接口,在我们进行(map,join,filter)这些操作的时候,这个接口会将相同的操作作用到处于不同节点的数据集上,这就可以使得他们能够记录创建数据集的血统(lineage),而不是真正的去存储数据,从而提高数据的容错性。当我们某个RDD分区丢失的时候,RDD有足够的记录来去重新计算,并且只需要计算该丢失数据分区的数据,这样数据恢复的代价就会小很多。
  基于RDD的机制,spark实现了多类的模型计算,比如:

  • 迭代计算:目前应用于图处理,数值优化,机器学习的算法。
  • 交互式的SQL查询:相比较MR,spark SQL提供完善的SQL机制,能够使人们以更小的学习成本接触到spark。
  • MapReduceRDD:通过提供MR的超集,来高效执行MR程序。
    13.流式数据处理:spark提供了离散数据流(D-Stream)来解决流式计算的问题。它把流式计算的执行当成了许多批次的批数据处理序列,并且将状态保存在RDD中,依赖关系图进行计算。
      类型划分
      在spark中RDD 的类型划分这里,很多接触过的都知道,普片划分都是划分为两种,一种是Transformation的RDD,一种是Action的RDD。但这里,我还是想根据spark的任务操作具体划分为四种类型(两种和四种只是角度不同,没有区别),分别是:创建类型的RDD,转换类型的RDD,以及控制类型的RDD和行为类型的RDD。下面看一下我的具体解释:

  • 创建类型:通常适用于创建RDD的。RDD的创建只有两种方法,一种是来自于内存集合和外部存储系统,另外就是通过转换生成的RDD。

  • 转换类型:将RDD通过一定的操作编程新的RDD,RDD得转换操作是一个惰性操作,他没有立即执行,而只是定义了一个新的RDD。
  • 控制类型:进行RDD持久化,可以让RDD 按照不同的存储策略保存在磁盘或者内存中,例如cache接口。
  • 行动类型:能够出发spark任务运行,例如对RDD进行coolect就是行动类型。行动类型也分为两类:一类是吧结果变成scala集合或者变量接受,另一种就是将RDD保存到外部文件系统或者数据库中。
      看完了RDD的简介和类型划分,咱们接着看一下在spark任务调度中的RDD是怎么流转实现的:
      首先看一下作业调度:
    当我们对RDD执行转换操作的时候,调度器会根据RDD的“血统”来构建由若干调度阶段(stage)组成的有向无环图(DAG),每一个stage都是尽可能多的包含窄依赖转换。调度器会根据DAG图的顺序进行计算,并最终得到目标的RDD。举个例子,来形象的说明一下整个spark任务中的任务划分和调度流程。
    一段简单的Wordcount的代码,咱们具体分析一下整个流程,结合图。
      代码如下:
sc.textFile("/root/spark/spark-2.3.1-bin-hadoop2.7/README.md").flatMap(_.split(" ")).map(x=>(x,1)).reduceByKey(_+_).map(x=>(x._2,x._1)).sortByKey(false).map(x=>(x._2,x._1)).take(10)

这是一段简单的Wordcount的代码,读取的是spark2.0 的安装包里面的README.md文件。对于不怎么熟悉scala或者spark的人,我简单解释一下这段代码的作用,熟悉的请忽略。
  这段代码是从一个path读取了一个README.md文件,这个文件里面是一个一个的英文单词,他们之间使用空格格开的,读取了之后把这个文件里面的内容压平,按照空格切分,然后把每个单词和数字1组成元组例如(run,1),即想办法把每一个出现的单词和数字一拼在一个元组里,好进行数据统计。和数字1组成元组之后,调用reduceByKey,让每一个单词相同的后面的数字进行相加,然后使用map方法,把数字和单词调换位置,调换之后按照数字进行排序,排完序之后,在调换回来,拿出前十个,输出。
  我这里直接用的spark-shell的方式提交的这个任务,简单的本机的测试用来解释。

  这是输出结果:

scala> sc.textFile("/root/spark/spark-2.3.1-bin-hadoop2.7/README.md").flatMap(_.split(" ")).map(x=>(x,1)).reduceByKey(_+_).map(x=>(x._2,x._1)).sortByKey(false).map(x=>(x._2,x._1)).take(10)
res2: Array[(String, Int)] = Array(("",71), (the,24), (to,17), (Spark,16), (for,12), (##,9), (and,9), (a,8), (can,7), (run,7))

在一启动这个任务的时候,控制台就会打印出stage的情况,我这里就两个stage,同时,在spark的UI界面也会有DAG图,可以清楚的查看各个stage的运行情况。
控制台的打印结果
spark的UI界面描述
DAG图
在spark的UI界面图里,你可以点进去看一下你跑的程序的各种情况,例如GC时间,各个task的运行情况,你集群本身的资源情况等等,这里就不做多的叙述。
  RDD的存储策略
  spark中对于持久化RDD的问题,提供了三种存储策略:一些未序列化的JAVA对象存在内存中、序列化的数据存于内存或者存储在磁盘中。对于性能来说,第一个存储是最优的选择,因为在进行数据访问的时候,可以直接访问在JAVA虚拟机内存的RDD对象;在内存空间有限的情况下,第二种方式可以让使用者采用比JAVA对象更有效的存储方式,但是降低了性能。第三种情况就是应用于RDD太大的情形,这种情形每一次计算RDD都会带来额外开销(磁盘IO)。
  你可能想知道,如果数据存储在内存中,那么spark是怎么进行内存中的垃圾管理呢?在spark中,对于内存的使用的是LRU回收算法来进行管理。没放计算得到一个新的RDD的分区,但没有足够的空间来存储时,系统就会从最近最少使用的RDD中回收其一个分区的空间。除非这个RDD是新分区产生的RDD,这种情况下会把这个RDD继续保留,防止同一个RDD被循环调入调出。
  检查点支持
  对于容错这方面来说,spark采用了大多数容错采用的手段,设立检查点。虽然spark中有“血统”可以用来错误后的RDD的恢复,但对于一些特别长的“血统”来说,恢复起来耗时还是特别长的,因此,需要设立检查点(checkpoint)来保存到外部存储中。
  通常情况下,对于包含宽依赖的长“血统”的RDD设置检查点是很有作用的,当我们集群中某个节点出现故障的时候,会使得各个父RDD的计算的数据出现丢失,造成重新计算,但是对于一些窄依赖的RDD,对其进行检查点的设立就很没有必要。spark对于检查点的设立提供的相应的API操作,可以让用户自己去操作定义哪些需要设立检查点,哪些不需要。
  spark的编程模型RDD的经验分享上部分就到这里了,这部分还是主要说了一下在spark中的基本操作单位RDD的一些基本的概念,下半部分的经验分享主要会说到一些偏实操的东西,比如如何区分RDD之间的依赖关系,以及实际操作中的RDD的一些具体的代码操作。

猜你喜欢

转载自blog.csdn.net/flyinthesky111/article/details/82503544