spark源码学习(九):map端计算结果缓存处理(一)
在前面我们谈到了在map任务结束之后,map任务会对结果进行三种方式的处理,这里来看看具体的代码,就是进入ExternalSorter的insertAll方法去看看。这里的代码主要分为三个部分,三个if代码块儿分别对应着在map端执行局合,直接写入partition的存储块儿,简单的对计算结果进行缓存。代码如下所示:
def insertAll(records: Iterator[_ <: Product2[K, V]]): Unit = {
val shouldCombine = aggregator.isDefined
if (shouldCombine) {
// 使用 AppendOnlyMap在内存中执行聚合
val mergeValue = aggregator.get.mergeValue
val createCombiner = aggregator.get.createCombiner
//使用到的key-value对
var kv: Product2[K, V] = null
//定义一个偏函数update,最重要的函数
val update = (hadValue: Boolean, oldValue: C) => {
if (hadValue) mergeValue(oldValue, kv._2) else createCombiner(kv._2)
}
while (records.hasNext) {
//这里主要用于从records中读取数据
addElementsRead()
kv = records.next()
//数据格式((partitionIndex,key),update(boolean,oldValue)
map.changeValue((getPartition(kv._1), kv._1), update)
//如果内存使用过大,就会把数据写入磁盘,同时新建SizeTrackingAppendMapOnly
maybeSpillCollection(usingMap = true)
}
}
else if (bypassMergeSort) {
//直接把结果写到partition文件中去
if (records.hasNext) {
spillToPartitionFiles(
WritablePartitionedIterator.fromIterator(records.map { kv =>
((getPartition(kv._1), kv._1), kv._2.asInstanceOf[C])
})
)
}
}
else {
//对map端的结果数据进行简单的缓存
while (records.hasNext)
{
addElementsRead()
val kv = records.next()
buffer.insert(getPartition(kv._1), kv._1, kv._2.asInstanceOf[C])
maybeSpillCollection(usingMap = false)
}
}
}
发现在代码的刚刚开始的地方出现;aggregator.get.***,这是什么东西,进入源码看看如下
这里会发现,其实他的三个构造参数都是三个函数,主要是用来对key,value数据块儿进行操作的,具体的不解释,上面的注释写的很清楚。如果进一步去看的话,你会发现这个class会出现在shuffleRDD中,会有一个setAggregator的方法,很明朗,我们这里在定义Aggregator的时候使用的是case class,会自动的提供set,get方法。要是继续的深入去看,我们就应该去看看PairRDDFunctions中的combinByKey函数啦,这个方法会显示出函数式编程的巨大威力。在这里不进行讨论combinebykey,点击这里去看看其他大神的介绍。
上面的代码中的第一个代码块儿已经介绍过了,就是把数据在map端就直接进行合并排序,这样可以大幅度减少reduce的压力,那么接下来就继续看看第二代码块儿的内容--不适用map端的缓存,也不执行聚合和排序,而是直接把数据spill到各自对应的partition中去,让reduce来处理。如图所示:
最核心的代码当然就是SpillToPartitionFiles啦,主要用于溢出分区文件,就是把数据从缓存写到磁盘上。代码如下
进入上面代码的最后一句,看看这个迭代器是干什么的:
回到最上面的代码块儿,我们在来看看那个fromIterato的用法和具体的参数:
你会发现,这里的recordes也是一个iterator,只不过加上了视图界定而已。
还要注意这句话:writer.write(cur._1._2, cur._2)写如的数据格式,在代码fromiterator中的数据格式是Iterator[((Int, _), _)这样的,但是在写write方法的时候却直接忽略了partitionIndex,直接key,value写入磁盘,没有理会分区索引,所以,最终的结果如下图所示:
哈哈,不再有分区partition了吧。这种情况会把bucket合并到同一个文件,节约了磁盘I/O,最终才能提升性能,到这里,刚开始的那块儿代码的第二个判断宽块儿就介绍完了。第三个下片继续。