这是我参与「第四届青训营 -大数据场」笔记创作活动的第8篇笔记
本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
Shuffle算子
Spark中会产生Shuffle的算子大概可以分为4类
Spark中对shuffle的抽象
- 窄依赖:父RDD的每个分片至多被子RDD中的一个分片所依赖
- 宽依赖:父RDD中的分片可能被子RDD中的多个分片所依赖
算子内部的依赖关系
Shuffle Dependency
- 构造:
-
A single key-value pair RDD
-
partitioner:
- numberPartitions接口
- getPartition接口
-
serializer
-
keyOrdering
-
aggregator
- createCombiner:只有一个value的时候初始化的方法
- mergeValue:合并一个value到Aggregator
- mergeCombiners:合并两个Aggregator
-
mapSideCombine
-
Shuffle过程
触发流程
- Collect Action:
- SubmitJob:
- GetDependencies:
- RegisterShuffle:
Shuffle Handle的创建
Register Shuffle做的最重要的事情是根据不同条件创建不同的Shuffle Handle
Shuffle Handle与Shuffle Writer的对用关系
BypassMergeSortShuffleWriter实现细节
BypassMergeSortShuffleWriter和Hash Shuffle中的HashShuffleWriter实现基本一致, 唯一的区别在于,map端的多个输出文件会被汇总为一个文件。 所有分区的数据会合并为同一个文件,会生成一个索引文件,是为了索引到每个分区的起始地址,可以随机 access 某个partition的所有数据。
但是需要注意的是,这种方式不宜有太多分区,因为过程中会并发打开所有分区对应的临时文件,会对文件系统造成很大的压力。
具体实现就是给每个分区分配一个临时文件,对每个 record的key 使用分区器(模式是hash,如果用户自定义就使用自定义的分区器)找到对应分区的输出文件句柄,直接写入文件,没有在内存中使用 buffer。 最后copyStream方法把所有的临时分区文件拷贝到最终的输出文件中,并且记录每个分区的文件起始写入位置,把这些位置数据写入索引文件中。
UnsafeShuffleWriter实现细节
- 将record进行分区并序列化后插入sorter。
- 将record进行排序,并在排序完成后写入磁盘文件作为spill file,再将多个spill file合并成一个输出文件。
@Override
public void write(scala.collection.Iterator<Product2<K, V>> records) throws IOException {
// Keep track of success so we know if we encountered an exception
// We do this rather than a standard try/catch/re-throw to handle
// generic throwables.
boolean success = false;
try {
//将record进行分区并序列化后插入sorter
while (records.hasNext()) {
insertRecordIntoSorter(records.next());
}
//将record进行排序,并在排序完成后写入磁盘文件作为spill file,再将多个spill file合并成一个输出文件。
closeAndWriteOutput();
success = true;
} finally {
if (sorter != null) {
try {
sorter.cleanupResources();
} catch (Exception e) {
// Only throw this error if we won't be masking another
// error.
if (success) {
throw e;
} else {
logger.error("In addition to a failure during writing, we failed during " +
"cleanup.", e);
}
}
}
}
}
SortShuffleWriter实现细节
SortShuffleWriter实现过程
- 首先无法对map输出进行写出优化时(Serialized或者BypassMergeSort),匹配到SortShuffleWriter。
- 调用SortShuffleWriter的write方法开始写出准备。
- 创建ExternalSorter对map输出进行排序及合并写出到磁盘。
- 写出索引文件(记录分区在对应磁盘文件中的Segment偏移)。
SortShuffleWriter在写入时,会根据是否有mapSideCombine选择使用不同的数据结构来进行排序。有mapSideCombine,那么采取map或buffer。
归并排序分为两个阶段,第一阶段是分片输出有序文件,第二阶段是归并输出整体有序文件。
Reader实现
网络时序图
使用基于netty的网络通信框架
位置信息记录在MapOutputTrack中
主要会发送两种类型的请求:openBlocks请求、Chunk请求或Stream请求
ShuffleBlockFetchIterator
区分local和remote节省网络消耗
详解见[:]([SPARK][CORE] 面试问题之 Shuffle reader 的细枝末节 (下) - 知乎 (zhihu.com))
External Shuffle Service
ESS作为一个存在于每个节点上的agent为所有Shuffle Reader提供服务,从而优化了Spark作业的资源利用率,MapTask在运行结束后可以正常退出
下图展示了一个 shuffle 操作,有两台主机分别运行了三个 Map 节点。Map 节点生成完 shuffle 数据后,会将数据的文件路径告诉给 ExternalShuffleService。之后的 Reduce 节点读取数据,就只和 ExternalShuffleService 交互。
Shuffle 优化使用的技术
Zero Copy
零拷贝指的是,从一个存储区域到另一个存储区域的copy任务没有CPU参与。零拷贝通常用于网络文件传输,以减少CPU消耗和内存带宽占用,减少用户空间(用户可以操作的内存缓存区域)与CPU内核空间(CPU可以操作的内存缓存区域及寄存器的拷贝过程,减少用户上下文(用户状态环境)与CPU内核上下文(CPU内核状态环境)间的切换,提高系统效率
Shuffle中合并文件、发送数据会用到zero copy