BypassMergeSortShuffleWriter实现了Hash风格的Shuffle机制,和已经废弃的HashShuffleWriter类似。这个shuffle writer将传入的record写入单独的文件,每个reduce partition对应一个文件,然后拼接这些文件汇总成一个单独的输出文件。这个输出文件按照partitionId分成多个区域,并生成相应的索引文件,Reducer会根据索引问价中各个partitionId的偏移量来fetch对应的数据。
BypassMergeSortShuffleWriter应用场景
从BypassMergeSortShuffleWriter
的原理中我们可以看到,这种Writer的在存在大量的reduce partition时,是低效率的,因为它会同时对每个partition都打开一个serializer和文件流,另外它是直接写入文件,不需要内存操作,所以排序和map-side聚合是不能指定的,所以使用时候需要满足以下几个条件:
- 不能指定ordering,即在partition内不能排序;
- 不能指定Aggregator,即不能进行map端的聚合;
- parttition的数量要小于
spark.shuffle.sort.bypassMergeThreshold
指定的阈值,默认200.
对应的源码分析如下:
// org.apache.spark.shuffle.sort.SortShuffleWriter
// 用于判断是否绕开合并和排序
def shouldBypassMergeSort(conf: SparkConf, dep: ShuffleDependency[_, _, _]): Boolean = {
if (dep.mapSideCombine) {
// 启用了Map端Combine,则不可使用BypassMergeSort
require(dep.aggregator.isDefined, "Map-side combine without Aggregator specified!")
false
} else {
// 如果ShuffleDependency的mapSideCombine属性为false,
// 且ShuffleDependency的分区计算器中的分区数量小于等于bypassMergeThreshold,返回true
val bypassMergeThreshold: Int = conf.getInt("spark.shuffle.sort.bypassMergeThreshold", 200)
dep.partitioner.numPartitions <= bypassMergeThreshold
}
}
整体架构
该种ShuffleWriter原理如下图所示:
从图中我们可以看出经过以下步骤:
- 首先根据分区函数,将不同分区的数据写到不同的分区文件中;
- 将多个分区文件按照分区id从小到大的顺序依次写入到数据文件中,并得到各个分区的大小;
- 根据各个分区的大小,生成相应的索引文件。
下面我们来具体看下写入过程。
文件写入
write
方法将迭代器中的数据写入到文件中,实现如下:
- 首先检查迭代器中是否有数据,没有数据,直接生成索引文件返回即可;
- 如果有数据,则首先为每个partition创建一个
临时文件
和一个DiskBlockobjectWriter
,DiskBlockobjectWriter
用于写数据到该临时文件中; - 遍历迭代器中数据,对每个
record
使用分区函数计算出相应的分区ID,然后调用此分区ID对应的DiskBlockObjectWriter
的write
方法,向临时Shuffle文件的输出流中写入键值对; - 遍历完成后,提交缓冲区中的数据,全部写入磁盘,得到各个分区的文件信息;
- 通过
writePartitionedFile
合并多个文件,合并每个partition对应的文件到一个Temp文件中; - 使用
shuffleBlockResolver
来生成Block文件对应的索引文件和最终的数据文件,其中会校验文件大小和各个区的大小,然后生成最终的数据和索引文件。
public void write(Iterator<Product2<K, V>> records) throws IOException {
assert (partitionWriters ==