即使采用pipeline的方式,函数f对依赖的RDD中的数据集合的操作也会有两种方式:
1.f(record),f作用于集合的每一条记录,每次只作用于一条记录
2.f(records),f一次性作用于集合的全部数据;
Spark采用的是第一种方式,因为:
1.无需等待,可以最大化的使用集群的计算资源
2.减少OOM的产生
3.最大化的有利于并发
4.可以精准的控制每一个Partition本身(Dependency)及其内部的计算(compute)
5.基于lineage的算子流动式函数式计算,可以节省中间结果的产生,可以最快的恢复
不会产生网络流量,因为用的是pipeline。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
物理执行过程
Spark Application里面可以产生1个或者多个job,例如spark-shell默认启动时,内部就没有job,只是作为资源的分配程序,可以在里面写代码产生多个Job,普通程序一般而言,可以有不用的Action,每一个Action一般也会触发一个Job。
Spark是MapReduce思想的一种更加精致和高效的实现,MapReduce有很多不同的具体实现,例如Hadoop的MapReduce基本的计算流程,如下:首先是并发,以JVM为对象的并发Mapper,Mapper中的map的执行会产生输出数据,输出的数据会经由Partitioner指定的规则,放到localFileSystem中,然后再经由Shuffle、Sort、Aggregate变成reducer中的Reduce的输入,执行reduce产生最终的执行结果。hadoop MapReduce执行的流程虽然简单,但是过于死板,尤其是构造复杂算法(迭代)时候,非常不利于算法的实现,且执行效率极为低下。
Spark执行时,物理算法构造和物理执行时,最基本的核心:最大化pipeline
基于pipeline的思想,数据被使用的时候才开始计算,从数据流动的视角来说,是数据流动到计算的位置。实质上,从逻辑的角度来看,是算子在数据上流动。
从算法构建的角度而言,是算子作用于数据,所以是算子在数据上流动。方便算法的构建。
从物理执行的角度而言,是数据流动到计算的位置。方便系统更加高效的运行。
对于pipeline而言,数据计算的位置就是每个Stage中最后的RDD,每个Stage中除了最后一个RDD算子是真实的意外,前面的算子都是假的。
由于计算的Lazy特性,导致计算从后往前回溯,形成Computing Chain,导致的结果就是需要首先计算出具体一个Stage内部左侧的RDD中本次计算依赖的Partition。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
窄依赖的物理执行
一个Stage内部的RDD都是窄依赖,窄依赖计算本身是逻辑上看从stage内部的最左侧的RDD开始计算的,根据Computing Chain,数据(Record)从一个计算步骤流动到下一个计算步骤,以此类推,直到计算到Stage内部的最后一个RDD产生计算结果。
Computing Chain的构建是从后往前回溯构建而成的,而实际的物理计算则是让数据从前往后在算子上流动,直到流动到不能再流动为止,才开始计算下一个Record。这就导致后面的RDD对前面的RDD的依赖,虽然是Partition级别的数据集合的依赖,但是并不需要父RDD把Partition中的所有的Record计算完毕,才整体完后流动数据进行计算。这极大地提高了计算速率。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
宽依赖的物理执行
必须等到依赖的父Stage中的最后一个RDD把全部数据彻底计算完毕,才能够经过shuffle来计算当前的Stage。