Spark Shuffle

绪论

  人们对Spark的印象往往是基于内存进行计算,但实际上来讲,Spark可以基于内存、也可以基于磁盘或者是第三方的储存空间进行计算,背后有两层含意,第一、Spark框架的架构设计和设计模式上是倾向于在内存中计算数据的,第二、这也表达了人们对数据处理的一种美好的愿望,就是希望计算数据的时候,数据就在内存中。
在这里插入图片描述
  在这个过程中一方面是 Driver 跟 Executor 进行网络传输,另一方面是Task要从 Driver 抓取其他上游的 Task 的数据结果,所以有这个过程中就不断的产生网络结果。其中,下一个 Stage 向上一个 Stage 要数据这个过程,我们就称之为 Shuffle。

  思考点:上一个 Stage 为什么要向下一个 Stage 发数据?
  假设现在有一个程序,里面有五个 Stage,我把它看成为一个很大的 Stage,在分布式系统中,数据分布在不同的节点上,每一个节点计算一部份数据,如果不对各个节点上独立的部份进行汇聚的话,我们是计算不到最终的结果。这就是因为我们需要利用分布式来发挥它本身并行计算的能力,而后续又需要计算各节点上最终的结果,所以需要把数据汇聚集中,这就会导致 Shuffle,这也是说为什么 Shuffle 是分布式不可避免的命运

  因为想利用分布式的计算能力,所以要把数据分散到不同节点上运行,上游阶段数据是并行运行的,下游阶段要进行汇聚,所以出现Shuffle。如果下游分成三类,上游也需要每个Task把数据分成三类,虽然有可能有一类是没有数据,这无所谓,只要在实际运行时按照这套规则就可以了,这就是最原始的 Shuffle 过程。

  绪论转载自:https://blog.csdn.net/zhanglh046/article/details/78360762

HashShuffle

 HashShuffle引入

  下面通过一个问题来引入HashShuffle

  问:每一个key对应的value不一定都是在一个partition中,也不太可能在同一个节点上,因为RDD是分布式的弹性的数据集,他的partition极有可能分布在各个节点上。如何聚合?

  答:
  Shuffle Write:上一个stage的每个map task就必须保证将自己处理的当前分区中的数据相同的key写入一个分区文件中,可能会写入多个不同的分区文件中。
  Shuffle Read:reduce task就会从上一个stage的所有task所在的机器上寻找属于自己的那些分区文件,这样就可以保证每一个key所对应的value都会汇聚到同一个节点上去处理和聚合。
  在这里插入图片描述
  HashShuffle分区依据:每一个task的计算结果根据key的hashcode与reduce task的个数取模决定写入到哪一个分区文件。这样就能保证相同的数据一定落到某一个分区中。

 未优化的HashShuffle

  目前我们使用的HashShuffle是经过优化后的,那么原始的HashShuffle是什么样的呢?下面我们通过分析这张图来简单介绍。在这里插入图片描述
  在HashShuffle没有优化之前,每一个map task会为每一个reduce task创建一个bucket缓存,并且会为每一个buffer创建一个block file文件。这个bucket存放的数据就是经过HashPartitioner操作之后,找到对应的bucket然后放进去,最后将bucket中的缓存数据溢写到磁盘上,即对应的block file。

  这样操作的缺点显而易见,就是会产生大量的小文件和占用大量的bucket缓存。假设有100个map、100个task,那么就会产生100*100=10000个小文件和10000个buffer缓存。

  buffer缓存很重要,需要将map task所有数据都写入bucket,才会刷到磁盘,那么如果Map端数据过多,这就很容易造成内存溢出(OOM)。尽管后面有优化,bucket写入的数据达到刷新到磁盘的阀值之后,就会将数据一点一点的刷新到磁盘,但是这样磁盘I/O就多了。

 优化后的HashShuffle

  优化后的HashShuffle主要是增加了bucket和block file的复用操作。在这里插入图片描述
  每一个Executor进程根据核数,决定Task的并发数量,比如executor核数是2,就是可以并发运行两个task,如果core是一个则只能运行一个task。

  假设executor核数是1,map task数量是M,那么它依然会根据reduce task的数量x,创建x个bucket缓存,然后对key进行hash,数据进入不同的bucket中,每一个bucket对应着一个block file,用于刷新bucket缓存里的数据

  与优化前每个task不断创建bucket和block file相比。下一个task运行的时候,不会再创建新的bucket和block file,而是复用之前的task已经创建好的bucket和block file。即同一个Executor进程里所有Task都会把相同的key放入相同的bucket缓冲区中。

  同样的假设,假设有100个map、100个task。那么未优化前就会产生100*100=10000个小文件,优化后会产生1*100=100个小文件。在我们这个例子中,使用优化后的HashShuffle产生的小文件的数量减少了100倍。但是,尽量小文件的数量骤减,如果reduce过多也会产生很多的小文件。

SortShuffle

 SortShuffle引入

  因为HashShuffle会产生大量的磁盘小文件,并且会占用大量的缓存。为了解决这些问题Spark引入了SortShuffle的概念,SortShuffle中map task不会为后续的操作创建单独的文件,而是把所有结果写入同一个文件,同时生成一个索引文件。

  以前的数据是写入在内存中,等到数据全部写入完毕,才把这些数据溢写到磁盘上。现在,为了减少内存的使用,在内存不足时直接将输出溢写到磁盘。写入完成的时候,把磁盘上的数据和内存中的数据一起进行归并,从而减少了内存的使用,同时也显著减少了小文件的数量。

 普通运行机制

在这里插入图片描述
  在该机制下,聚合算子一边通过map局部聚合,一边写入内存。当内存中的数据达到一定的阈值之后,就会将内存中的数据写入到磁盘,清空内存数据。

  写入到磁盘文件之前会先根据key值进行sort排序,排序后的数据分批写入磁盘(默认10000条数据一批),写入磁盘文件通过缓冲区溢写的方式,每次溢写都会产生一个磁盘小文件。也就是说,一个task过程会产生多个临时文件。

  最后,在每个task中,将所有的临时文件合并,即merge过程,这个过程就是把所有的临时文件读出来,写入到最终文件。所以,一个task产生的所有数据都在这一个最终文件中。同时会生成一个索引文件(标识reduce task中的数据在文件中的索引)。

 bypass运行机制

在这里插入图片描述
  bypass机制的运行条件:shuffle reduce task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值。

  主要用于处理不需要排序和聚合的Shuffle操作,所以数据是直接写入文件,数据量较大的时候,网络I/O和内存负担较重。

  在这种机制下,当前stage的map task会为每个reduce task都创建临时磁盘文件。将数据按照key值进行hash,然后根据hash值,将key写入对应的磁盘文件中。最终,同样会将所有临时文件依次合并成一个磁盘文件,建立索引。

  该机制与sortshuffle的普通机制相比,在readtask不多的情况下,首先写的机制是不同,其次不会进行排序。这样就可以节约一部分性能开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值