大数据生态体系产品(2) - Spark的架构与原理

1. Spark的诞生

  Spark在2012年左右开始流行,那时内存的容量提升和成本降低已经比MapReduce出现的十年前强了一个数量级,Spark优先使用内存的条件已经成熟;其次,使用大数据进行机器学习的需求越来越强烈,不再是早先年那种数据分析的简单计算需求。而机器学习的算法大多需要很多轮迭代,Spark的stage划分相比Map和Reduce的简单划分,有更加友好的编程体验和更高效的执行效率。

  实际上在Spark诞生前,大家并没有觉得Hadoop的MapReduce慢,毕竟是做大数据计算,执行时间也还在能够接受的范围之内。直到Spark的出现,通过对比,人们才发现,原来可以快这么多。

  除了速度快外,Spark还有简单易用的编程模型。

  使用Scala语言在Spark上编写WordCount程序,主要代码只需如下三行:

val textFile = sc.textFile("hdfs://...")
val counts = textFile.flatMap(line => line.split(" "))
                 .map(word => (word, 1))
                 .reduceByKey(_ + _)
counts.saveAsTextFile("hdfs://...")

  第1行代码:根据HDFS路径生成一个输入数据RDD。

  第2行代码:在输入数据RDD上执行3个操作,得到一个新的RDD。

  • 将输入数据的每一行文本用空格拆分成单词;
  • 将每个单词进行转换,word => (word, 1),生成 的结构;
  • 相同的Key进行统计,统计方式是对Value求和,(_ + _);

  第3行代码:将这个RDD保存到HDFS。

2. 弹性数据集RDD

  RDD是Spark的核心概念,它既是Spark面向开发者的编程模型,又是Spark自身架构的核心元素。

  大数据计算就是在大规模的数据集上进行一系列的数据计算处理。MapReduce针对输入数据,将计算过程分为两个阶段,Map和Reduce,可以理解成是面向过程的大数据计算。我们在用MapReduce编程的时候,思考的是,如何将计算逻辑用Map和Reduce两个阶段实现,map和reduce函数的输入和输出是什么。

  Spark是直接针对数据进行编程,将大规模数据集合抽象成一个RDD对象,然后在这个RDD上进行各种计算处理,得到一个新的RDD,继续计算处理,直到得到最后的结果数据。Spark可以理解成是面向对象的大数据计算。

  我们在进行Spark编程的时候,思考的是一个RDD对象需要经过什么样的操作,转换成另一个RDD对象,思考的重心和落脚点都在RDD上。

  重新看上面的代码实际上是执行了3次RDD的转换,因为转换的RDD可以继续调用RDD的转换函数,所以连续写成了一行代码。

  RDD上定义的函数分为2种:

  • 转换函数(transformation):返回值还是RDD
  • 执行函数(action):不再返回RDD

  RDD上的转换操作分成两种:

  • 转换操作产生的RDD不会出现新的分片:例如map/filter等,经过转换后结果还在当前分片上,RDD只会在产生新的RDD分片的时候才会真正生成一个RDD,这也是Spark的特性,称为惰性计算
  • 转换操作产生的RDD则会产生新的分片:例如reduceByKey,来自不同的分片的相同Key必须聚合在一起操作,产生新的RDD分片

  Spark应用程序代码中的RDD和Spark执行过程中生成的物理RDD不是一一对应的。

3. Spark的生态体系

  以Spark为基础,有支持SQL语句 Spark SQL,有支持流计算的Spark Streaming,有支持机器学习的MLlib,还有支持图计算的GraphX。利用这些产品Spark技术栈支撑起大数据分析、大数据机器学习等各种大数据应用场景。

Spark的生态体系

4. Spark高效计算的原理剖析

4.1 计算阶段

  MapReduce一个应用一次只运行一个map和一个reduce。

  Spark可以根据应用的复杂程度,分割成更多的计算阶段(stage),这些计算阶段组成一个有向无环图DAG,Spark任务调度器可以根据DAG的依赖关系执行计算阶段。

  以机器学习中的逻辑回归性能为示例,我们发现Spark比MapReduce快100多倍,因为机器学习算法需要进行大量的迭代计算,产生数万个计算阶段,这些计算阶段Spark在1个应用中处理完成,而MapReduce需要启动数万个应用。

  有向无环图-DAG:不同阶段的依赖关系是有向的,计算过程只能沿着依赖关系方向执行,被依赖的阶段执行完成之前,依赖的阶段不能开始执行,同时,这个依赖关系不能有环形依赖,否则会成为死循环。

  下面这张图描述了一个典型的Spark运行DAG的不同阶段:

Spark运行DAG的不同阶段

  整个应用被切分成3个阶段,阶段3需要依赖阶段1和阶段2,阶段1和阶段2互不依赖。Spark在执行调度的时候,先执行阶段1和阶段2,完成以后,再执行阶段3。如果有更多的阶段,Spark的策略也是一样的。只要根据程序初始化好DAG,就建立了依赖关系,然后根据依赖关系顺序执行各个计算阶段,Spark大数据应用的计算就完成了。

  负责Spark应用DAG生成和管理的组件是DAGScheduler,它根据程序代码生成DAG,然后将程序分发到分布式计算集群,按计算阶段的先后关系调度执行。

4.2 shuffle

  有的函数有时候有shuffle,有时候没有。比如上图例子中RDD B和RDD F进行join,得到RDD G,这里的RDD F需要进行shuffle,RDD B就不需要。

  不需要进行shuffle的依赖,在Spark里被称作窄依赖;
需要进行shuffle的依赖,被称作宽依赖。

  跟MapReduce一样,shuffle也是Spark最重要的一个环节,只有通过shuffle,相关数据才能互相计算,构建起复杂的应用逻辑。

  虽然其本质依然是Map和Reduce,但这种多个计算阶段依赖执行的方案可以有效减少对HDFS的访问,减少作业的调度执行次数,因此执行速度也更快。

  Hadoop MapReduce主要使用磁盘存储shuffle过程中的数据不同,Spark优先使用内存进行数据存储,包括RDD数据。除非是内存不够用了,否则是尽可能使用内存, 这也是Spark性能比Hadoop高的另一个原因。

5. Spark的执行过程

  Spark支持Standalone、Yarn、Mesos、Kubernetes等多种部署方案,几种部署方案原理也都一样,只是不同组件角色命名不同,但是核心功能和运行流程都差不多。

Spark的执行过程
  上图为Spark的执行过程,执行情况如下:

  1. Spark应用程序启动在自己的JVM进程里,即Driver进程,启动后调用SparkContext初始化执行配置和输入数据;
  2. SparkContext启动DAGScheduler构造执行的DAG图,切分成最小的执行单位也就是计算任务;
  3. Driver向Cluster Manager请求计算资源,用于DAG的分布式计算;
  4. Cluster Manager收到请求以后,将Driver的主机地址等信息通知给集群的所有计算节点Worker;
  5. Worker收到信息以后,根据Driver的主机地址,跟Driver通信并注册,然后根据自己的空闲资源向Driver通报自己可以领用的任务数;
  6. Driver根据DAG图开始向注册的Worker分配任务;
  7. Worker收到任务后,启动Executor进程开始执行任务;
  8. Executor先检查自己是否有Driver的执行代码,如果没有,从Driver下载执行代码,通过Java反射加载后开始执行;

猜你喜欢

转载自blog.csdn.net/initiallht/article/details/124918236
今日推荐