从另一个角度观察Spark Shuffle过程

本文深入探讨了Spark Shuffle过程中各组件之间的协作机制,详细分析了Application, Job, Stage, Task等模块间的互动关系,并重点讲解了Shuffle中间文件的定位流程。此外,还解释了RDD缓存与Shuffle的关系。

大部分将Spark Shuffle的文章,都是从读写过程,与MapReduce对比等角度来阐述的,容易割裂开各模块在Shuffle过程中的关系及相互配合的联系。

本人在读这部分源码的过程中,认为理清Applicatoin, Job, Stage, Task, ApplicationMaster, Executor这些模块间的协作,及如何完成Shuffle中间文件的定位,是比较重要的,可以让人有整体方向性的了解和把握。

一张图,解释了各模块间的关系:



上图是shuffle阶段各模块协作关系图,也阐述了Shuffle Read的Stage查找定位Shuffle中间文件的流程。

做几点解释:

  1 shuffle_id是一个Application中全局唯一的,标识了一个源stage和shuffle后的目标stage的对应关系(源码中, 用

Dependency这个数据结构类表示)。特别地,设当Stage1在App生命周期内,会进行两次shuffle(两个不同的job的情况),分别对应Stage2和Stage3,那么这两次的shuffle过程会用不同的shuffle_id.

  2 MapStatus: 上面已经说过, shuffle_id是标识进行shuffle操作的前后stage的关系; 设stage1为源stage,stage2为目标stage; stage1有若干task,每个task作为一个单元独立进行计算,然后会将结果以MapStatus的数据结果的形式回传给MapOutputTracker; 所以, 一个shuffle_id下对应一个MapStatus数组,MapStatus在数据中的位置,就是这个MapStatus对应的task的编号;

  3 BlockManager: 一个MapStatus对应一个BlockManager; BlockManager是什么呢?它是executor中负责管理I/O相关操作的,也就是说, 一个executor对应一个BlockManager; 而一个NodeManager中会启动多个executor, 这些executor正常情况下的生命周期是整个Application(多个Job会共享executor)。所以呢, BlockManager中, 不但要有标识是哪个executor的executor_id,也要有标识executor位于哪个NodeManager的host+port信息。再回过来说, 如何理解一个MapStatus对应一个BlockManager呢? Stage1的Task在executor中执行,执行完毕后, 会将block文件写入当前executor程序执行所在的目录中, 并且将执行结果(是状态, 非数据)以MapStatus的形式返回给Driver的MapOutputTracker。所以说, 一个MapStatus标识了所对应的task,是在哪个executor中执行的,并且对应的是源Stage的第几个task。这样大家能明白了为什么说一个MapStatus对应一个BlockManager(即executor)了吧!但是如果反过来就不可以了, 一个BlockManager会对应多个MapStatus!因为一个BlockManager对应的executor,可能会执行同个stage的多个task啊!图中, MapStatus1和MapStatus2就对应同一个BlockManager。

  4 不同的Job, 会复用同一个executor, 即executor会执行不同的Job;

  5 同一个executor,会执行不同阶段的shuffle, 包括同一个job中的不同阶段的shuffle;

  6 一个Application生命周期中, 只会有一个App Master; 从源码上看, 当driver注册为App Master后, 才会运行用户自己写的spark计算代码;

  7 上面的4,5,6, 我认为是Spark比MapReduce快的一个原因, 因为执行了多个计算,只用启动一次App Master, 计算单元Executor也只会启动一次; Task只会在Executor的线程中执行,大大减少了进程冷启动和资源分配的时间.

  8 下面给说说Shuffle Write的中间结果文件的落盘逻辑了。设Stage1的task1执行计算部分,开始写shuffle的输出文件, 输出partition分成了10份;Stage2的task1要读取Stage1的输出partition1的数据; Stage1的task1在写输出文件时,会将10个partition的结果数据,写入到同一个输出文件中,这个文件叫做"block"文件, 文件的命名方式为: shuffleId_mapId_10data。同时,生成另一个index文件,记录每个分区的数据,在block文件中的起始偏移量。mapId就是task1在Stage1的序号,这里当然是1。

      这样,Stage2的task1,就能知道去哪里读取Stage1的task1的block文件了: Stage2从Dependency数据结构中,拿到shuffle_id,然后通过shuffle_id获得所有的MapStatus数据结构;然后就可以知道Stage1的第1个task(这里再强调一下,MapStatus数组元素对应的数组index,就是Mapstatise对应task在Stage中的序号; 这对于一个Stage的多个task跑在一个Executor上的情况,区分不同task的输出block文件,比较有用),对应在Executor上的block文件名是什么,然后通过远端读取,就可以拿到shuffle的block文件了。

 9 说一说我对RDD, cache, shuffle, Stage这几个名词的关系的理解。一开始学习Spark时,我对RDD cache很不理解,我认为,一个RDD被cache,相当于这个RDD的shuffle的结果被cache了。但是这就解释不了, 如果这个RDD被两个RDD都使用到,而且这两个使用的RDD的分区数不同,那么RDD的cache,是cache哪一种分区的结果呢?而且,shuffle的输出结果本来就已经写入磁盘了,为什么还需要做cache呢?读了源码之后,明白了,其实RDD的cache和Stage的shuffle是完全没关系的,即使RDD做了cache,还是需要启动一个ShuffleMapStage,这个Stage负责做Shuffle write的操作。从模块结构上来说,Stage包在了RDD的外面,读取RDD的数据,然后进行Shuffle Write的操作。

### 三级标题:Spark Shuffle 过程详解及其工作原理 SparkShuffle 是分布式计算过程中非常关键的一步,它用于在不同阶段(Stage)之间传递数据。Shuffle 操作主要包含两个阶段:Shuffle Write 和 Shuffle Read。在 Shuffle Write 阶段,数据按照指定的分区规则写入磁盘;在 Shuffle Read 阶段,下游任务从多个上游任务中读取属于自己的数据分区[^3]。 ShuffleMapTask 是负责执行 Shuffle Write 的核心组件,它将 Map 阶段的输出数据根据分区器(Partitioner)进行分区,并通过 ShuffleWriter 写入磁盘。这一过程通常会将数据写入每个工作节点的本地磁盘,默认路径为 `/shuffle` 目录,但可以通过配置参数修改[^4]。 在 Shuffle Write 阶段,Spark 支持两种主要的 ShuffleManage 模式:HashShuffleManage 和 SortShuffleManage。HashShuffleManage 每个分区都会生成一个临时文件,这种方式在分区数较多时会产生大量的小文件,从而影响性能。SortShuffleManage 则通过排序的方式将多个分区的数据合并写入一个文件,减少了文件数量并提高了 I/O 效率[^1]。 Shuffle Read 阶段的任务是根据自身的分区信息,从多个上游任务的输出中拉取对应的数据。这一过程涉及网络传输,数据需要从源节点传输到目标节点,这可能会带来一定的网络开销,影响作业的整体性能[^4]。 SparkShuffle 过程还可以在不排序 records 的情况下完成 shuffle write 和 shuffle read,这种机制将 shuffle 操作无缝融入 RDD 的 computing chain 中,同时兼顾内存与磁盘之间的平衡,并与 Hadoop MapReduce 的 shuffle 机制在实现细节上存在差异[^2]。 以下是一个简单的示例,展示如何在 Spark 中触发 Shuffle 操作: ```scala val data = sc.parallelize(Seq((1, "a"), (2, "b"), (3, "c"), (1, "d"))) val shuffled = data.reduceByKey(_ + _) // 触发 Shuffle 操作 shuffled.collect().foreach(println) ``` 在此示例中,`reduceByKey` 是一个典型的宽依赖操作,它会触发 Shuffle,将数据按照 Key 进行重分区并聚合。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值