【Spark】spark shuffle

spark虽然是基于内存计算的,但是它也会产生shuffle

首先我们需要知道,Spark任务会根据RDD之间的依赖关系,形成一个DAG有向无环图,DAG会提交给DAGScheduler,DAGScheduler会把DAG划分相互依赖的多个stage,划分stage的依据就是RDD之间的宽窄依赖。遇到宽依赖就划分stage,每个stage包含一个或多个task任务。那我们可以想一下,如果上一个RDD上的patition不在同一个节点上,那么它是如何进行聚合的呢?这时候就产生了shuffle。

【Spark】spark shuffle_第1张图片

spark shuffle

shuffle write:主要就是在一个stage结束计算之后,为了下一个stage可以执行shuffle类的算子(比如reduceByKey,groupByKey),而将每个task处理的数据按key进行“分区”。所谓“分区”,就是对相同的key执行hash算法,从而将相同key都写入同一个磁盘文件中,而每一个磁盘文件都只属于reduce端的stage的一个task。在将数据写入磁盘之前,会先将数据写入内存缓冲中,当内存缓冲填满之后,才会溢写到磁盘文件中去。

shuffle read:通常就是一个stage刚开始时要做的事情。此时该stage的每一个task就需要将上一个stage的计算结果中的所有相同key,从各个节点上通过网络都拉取到自己所在的节点上,然后进行key的聚合或连接等操作。由于shuffle write的过程中,task给Reduce端的stage的每个task都创建了一个磁盘文件,因此shuffle read的过程中,每个task只要从上游stage的所有task所在节点上,拉取属于自己的那一个磁盘文件即可。

        spark shuffle的发展史,spark1.2之前没有SortShuffle,spark1.2-spark1.6之间是有HashShuffle和SortShuffle的,spark2.0之后就只有SortShuffle了,HashShuffle被移除了

hashshuffle

1.普通机制

【Spark】spark shuffle_第2张图片

执行流程:

1)map task阶段每个task根据分区器对key取哈希值,然后对reduce task取模,将不同的结果写到不同的buffle中

2)buffle的默认大小是32kb,buffer起到的是缓存作用,缓存能够加速写磁盘,提高计算的效率

3)每个buffle中的数据最后会映射为一个个的磁盘小文件

4)reduce task根据不同的结果拉取对应的磁盘小文件,拉取的时候也是先拉取到buffer中,每次拉取的大小不能超过buffer缓冲大小(默认48kb),然后通过内存中的一个map来进行聚合,直到拉取完所有数据

缺点:

产生的磁盘小文件过多,会产生num(map)*num(reduce)个小文件

  1. 在Shuffle Write过程中会产生很多写磁盘小文件的对象。
  2. 在Shuffle Read过程中会产生很多读取磁盘小文件的对象。
  3. 在JVM堆内存中对象过多会造成频繁的gc,gc还无法解决运行所需要的内存 的话,就会OOM。
  4. 在数据传输过程中会有频繁的网络通信,频繁的网络通信出现通信故障的可能性大大增加,一旦网络通信出现了故障会导致shuffle file cannot find 由于这个错误导致的task失败,TaskScheduler不负责重试,由DAGScheduler负责重试Stage。

2.合并机制

【Spark】spark shuffle_第3张图片

执行流程:

1)map task阶段每个task根据分区器对key取哈希值,然后对reduce task取模,将不同的结果写到不同的buffer中。一个Executor上有多少个CPU core,就可以并行执行多少个task。不同的task,会把相同key的数据放到同一个buffer中,并将数据写入对应的磁盘文件内。

2)buffle的默认大小是32kb,buffer起到的是缓存作用,缓存能够加速写磁盘,提高计算的效率

3)每个buffle中的数据最后会映射为一个个的磁盘小文件

4)reduce task根据不同的结果拉取对应的磁盘小文件,拉取的时候也是先拉取到buffer中,每次拉取的大小不能buffer缓冲大小,然后通过内存中的一个map来进行聚合,知道拉取完所有数据

注意:

1)产生的磁盘小文件的数量=num(core)*num(recude)

2)启动HashShuffle的合并机制ConsolidatedShuffle的配置:

spark.shuffle.consolidateFiles=true

3)如果 Reducer 端的并行任务或者是数据分片过多的话则 Core * Reducer Task 依旧过大,也会产生很多小文件。

sort shuffle

1.普通机制

【Spark】spark shuffle_第4张图片

执行流程:

1)map task会将计算结果根据不同的算子写入到不同的内存数据结构中,reduceByKey这种聚合类的shuffle算子,那么会选用Map数据结构,一边通过Map进行聚合,一边写入内存;如果是join这种普通的shuffle算子,那么会选用Array数据结构,直接写入内存。内存数据结构大小默认为5MB

2)每写一条数据进入内存数据结构之后,就会判断一下,是否达到了某个临界阈值。如果达到临界阈值的话,那么就会尝试将内存数据结构中的数据溢写到磁盘,然后清空内存数据结构。在shuffle的时候会有一个定时器,定时器会检查内存数据结构的大小,如果内存数据结构空间不够,那么会申请额外的内存,申请的大小满足如下公式:applyMemory=nowMenory*2-oldMemory。申请不成功就会溢写,申请成功则继续存放数据。

3)溢写到磁盘之前会。根据key进行排序分区

4)然后开始溢写,溢写是以batch的形式分批进行,每次10000条数据。先将数据放到bufferOutPutStream中,BufferedOutputStream是Java的缓冲输出流,首先会将数据缓冲在内存中,当内存缓冲满溢之后再一次写入磁盘文件中,这样可以减少磁盘IO次数,提升性能

5)溢写到磁盘之后是一个个的磁盘小文件,然后对这些小文件进行marge合并成一个大文件。同时生成一个索引文件,其中标识了下游各个task的数据在文件中的start offset与end offset

6)reduce task拉取数据的时候,会先去解析索引文件,然后去拉取对应的数据

注意:

1)产生的文件数量=2*num(map task)

bypass机制

【Spark】spark shuffle_第5张图片

流程与不同机制类似,只是少了排序操作,启用该机制的最大好处在于,shuffle write过程中,不需要进行数据的排序操作,也就节省掉了这部分的性能开销。

触发条件:

  • .bypass运行机制的触发条件如下:
  • shuffle reduce task的数量小于spark.shuffle.sort.bypassMergeThreshold的参数值。这个值默认是200。
  • 不需要进行map端的预聚合,比如groupBykey,join.

.产生的磁盘小文件为:2*M(map task的个数)

shuffle 寻址

【Spark】spark shuffle_第6张图片

1)maptask 执行完毕后会把task执行结果和磁盘的小文件地址封装到MapStatus对象中,通过Executor中的MapOutputTrackerWorker对象向Driver中的MapOutputTrackerMaster汇报。MapOutputTracker使用来管理小文件地址的,。

2)reduce task拉取数据的时候会先在本地的MapOutputTackerWorker中的获取,如果没有则向Driver的MapOutputTackerWorker请求小文件地址

3)Driver会返回请求的磁盘小文件地址

4)获取到磁盘小文件地址后,会通过BlockManagerWorker中的ConnectionManager连接到对应的BlockManager

5)然后通过BlockManagerWorker中的BlockTransferService传输数据,默认启动5个task拉取数据,拉取数据量不可以超过48MB

reduce 中OOM如何处理?

  1. 减少每次拉取的数据量
  2. 提高shuffle聚合的内存比例
  3. 提高Excutor的总内存

你可能感兴趣的:(大数据,Spark)