文章目录

  • Spark与MapReduce对比误区
  • 1.Spark是内存计算,难道MapReduce不是基于内存计算的吗?
  • 2.Spark将中间结果保存到内存中了吗?
  • Spark RDD的执行逻辑
  • 3.Spark相比MapReduce可以减少磁盘IO吗?
  • Spark比MapReduce快在哪?


Spark与MapReduce对比误区

经常听到有人说Spark基于内存计算,将中间结果保存在内存中,避免了磁盘IO的次数。我觉得这句话的表面意思都对,但是很多人并没有了解其真正的含义。

知乎: https://www.zhihu.com/question/31930662

1.Spark是内存计算,难道MapReduce不是基于内存计算的吗?

什么是内存计算,如果是指把磁盘中的数据读取到内存中做计算的话,那么MapReduce肯定也是内存计算。Spark的特殊之处在于可以将RDD缓存到内存之中,下次再使用此RDD时,不用再次计算,而是直接从内存中获取。

2.Spark将中间结果保存到内存中了吗?

什么是中间结果?如果中间结果的含义不表达清楚,这个问题是没有意义的。而且必须要明白Spark的执行原理。

Spark RDD的执行逻辑

我们知道Spark可以将多个算子连接在一起,以“流水线”的方式进行计算。为什么要把流水线加上引号?因为我的理解,流水线处理应该是多个处理步骤连接,每个处理步骤接收上一个处理步骤的结果进行计算,然后再将处理结果输送给下一个处理步骤。并且每个处理步骤不断循环接收-处理-发送步骤,结束当前循环后,立即进行下一循环。那么Spark的处理是否为流水线处理呢?

Spark会将DAG划分为Stage,一个stage中包含不需要shuffle操作的RDD。那么连接的RDD是怎么执行计算任务的呢?每一个RDD都有一个compute函数,返回值为迭代器类型,函数内容一般为:获取上一个RDD compute函数生成的迭代器,构造本RDD生成的迭代器,在迭代器的next函数中,添加本RDD的处理逻辑。这也是为什么Spark RDD是惰性计算,因为每次连接的算子只是将计算逻辑添加在了 compute函数所构造的迭代器中,并没有实际计算。

比如:对于Map算子,返回了MapPartitionsRDD,其第二个构造参数是一个函数,即(TaskContext, Int, Iterator[T]) => Iterator[U]类型的函数,这个函数的逻辑很简单,调用Iterator的map函数,即对于 Iterator[T]获取的每个元素调用map算子输入的f: T => U函数进行处理,最后得到Iterator[U]。

spark hadoop mapreduce关系 spark mapreduce区别_迭代器


MapPartitionsRDD的compute函数中:获取firstParent[T].iterator,并调用构造参数中的(TaskContext, Int, Iterator[T]) => Iterator[U]函数对其进行转换。

spark hadoop mapreduce关系 spark mapreduce区别_数据_02


spark hadoop mapreduce关系 spark mapreduce区别_数据_03


由上我们可以了解到,一个Stage的的多个连接RDD的计算逻辑为,调用最后一个RDD的compute函数获取迭代器,调用此迭代器的next函数获取元素。此迭代器调用上一个迭代器的next函数获取元素,并进行自己的计算加工,然后进行返回。同理所有迭代器都是这样做的,直到数据源RDD。以文件数据源RDD为例,其next函数则是从文件中读取一行记录返回。

所以说RDD计算并不是一个真正的流水线过程,它更像是多层嵌套的函数。由于多层嵌套,每一层都要调用下层的函数获取结果,再进行本层的处理逻辑。那能不能把多层嵌套函数,直接变成一个函数呢?Spark的钨丝计算中的全阶段代码生成就是将多个连接的算子直接变成一个算子,避免了多层级的迭代器next函数的调用,减少了虚函数的调用的消耗,提高了计算效率。

那Spark中一个Stage的多个算子经过全阶段代码生成变成一个算子后,是不是就和MapReduce的Mapper算子一样了呢?我认为是的,多个连接的算子中如果没有shuffle,是可以写成一个算子的。所以Spark绕了一大圈最后回到了和MapReduce一样的效果。我认为Spark这样设计是为了使用者易于编程,因为使用者肯定希望将处理逻辑尽量划分为独立的小的多个算子,而不是写成一个复杂臃肿的一个算子。

综上可以看出,Spark的一个stage的内部处理流程和Mapreduce的Mapper、Reduce的内部处理流程是相同的,数据始终处于内存中计算。不同的是,Spark的RDD可以缓存,所以对于缓存的RDD其compute函数的迭代器直接从内存中获取,而不是继续向上一个RDD的迭代器获取。Spark和MapReduce对于shuffle操作都是需要写入磁盘中的,不可能保存在内存中。

所以Spark将中间结果保存在内存中吗?

  • 如果中间结果指的是数据在多个算子计算过程中的中间状态,是的;
  • 如果中间结果指的是缓存的RDD的计算结果,是的;
  • 如果中间结果指的是需要shuffle的数据,则不是的。

但要明白,只有缓存RDD可以使Spark的处理效率显著高于MapReduce,Spark和MapReduce都是在内存中计算数据的,shuffle都需要写入磁盘。

3.Spark相比MapReduce可以减少磁盘IO吗?

Spark和MapReduce的shuffle过程都是需要磁盘IO的,但是为什么总说Spark可以减少磁盘IO呢?这是因为DAG计算模型与MapReduce计算模型的区别。

MapReduce计算模型只包括Map和Reduce两个阶段,两个节点使用shuffle连接,那么如果在Reduce阶段数据还需要再一次shuffle,那应该怎么办呢?就只能结束当前Job,将数据写入磁盘。然后再启动一个Job读取数据进行操作。那Spark有何尝不是呢?只要需要shuffle,一定会写入磁盘。区别在哪里?

两个MapReduce Job的连接:
Map -> 磁盘IO -> Reduce -> 磁盘IO -> Map -> 磁盘IO ->Reduce。
Spark多个Stage连接:
stage -> 磁盘IO -> stage -> 磁盘IO -> stage -> 磁盘IO ->stage

为什么第一个Reduce和第二个Map之间的要进行磁盘IO?如果第二个Map的计算逻辑可以直接合并到第一个Reduce中,那么是完全不需要将第一个Reduce的结果写入磁盘中的。再开启一个MapReduce Job的原因一定是因为第一个Reduce和第二个Map之间需要数据shuffle。

那这跟Spark的stage连接有什么区别?每个stage之间都需要数据shuffle。这看起来似乎是相同的,其实本质上就是相同的。MapReduce 两个job之间的连接完全可以通过编写第一个Reduce和第二个Map的处理逻辑,使得第一个Reduce和第二个Map之间变成一个shuffle。那么这样其实就和Spark的计算逻辑是相同的。

但是没有人会这样做,因为实现shuffle的逻辑太复杂了,所以使用者一般都是先用第二个Map读取磁盘文件,然后利用MapReduce的shuffle过程去进行shuffle。这就造成了多了一次磁盘IO,究其原因是因为一次MapReduce Job只能进行一次shuffle。这在Spark中是没必要的,因为Spark DAG计算逻辑自然的包括了多个shuffle过程,不需要重新启动新的Spark job去进行操作。

所以综上来看,这是DAG模式和MapReduce模型的区别。MapReduce模型的局限性导致其必须进行更多的磁盘IO。通过设计第一个Reduce和第二个Map的逻辑将其变成shuffle,减少磁盘IO?这不是就Spark DAG吗,别人已经帮你做好了。

Spark比MapReduce快在哪?

Spark基于内存计算,将中间结果保存在内存中,避免了磁盘IO的次数。这句话没错,但这是Spark比MapReduce快的原因吗?从上面的分析中我们可以看出,并不准确,也不完全。

Spark真的比MapReduce快的地方在于:

  1. 缓存RDD
    中间计算的RDD结果缓存在内存中,当再次使用时,直接从内存中获取,而不是再次计算或从磁盘中获取。所以Spark适合迭代式的计算。
  2. DAG计算模型
    DAG计算模型可以实现单个job多次shuffle,而MapReduce一次job只能进行一次shuffle,所以对于需要多次shuffle的计算场景,Spark的磁盘IO次数少于MapReduce。
  3. Spark shuffle优化
    MapReduce在Shuffle时默认进行排序。Spark在Shuffle时则只有部分场景才需要排序。排序是非常耗时的。
  4. 多线程模型
    MapReduce采用了多进程模型,而Spark采用了多线程模型。多进程模型的好处是便于细粒度控制每个任务占用的资源,但每次任务的启动都会消耗一定的启动时间。Spark则是通过复用线程池中的线程来减少启动、关闭task所需要的开销。多线程模型也有缺点,由于同节点上所有任务运行在一个进程中,因此,会出现严重的资源争用,难以细粒度控制每个任务占用资源(借鉴别人的)。
  5. 钨丝计划
    Spark 钨丝计划中优化了对内存的使用,可以直接操作二进制数据,减少了GC,避免OOM。设计了缓存友好的算法,以及代码生成技术。

其实用第五点说明是不合适的,这是在Spark发展成熟之后的优化措施。Spark与MapReduce的本质差别还是在1、2、3点。

以上均为本人自己的理解,如果有错误请指出!