运行架构
MapOutputTracker是基于Master/Slave的架构,Master(Driver)负责存储当前Application上所有Shuffle的Map输出元数据信息,而Slave(Executor)可以通过rpc对Master上的Map输出状态信息进行查询。
注册Shuffle
在DAGScheduler使用createShuffleMapStage
方法为当前的ShuffleDependency创建对应的ShuffleMapStage时,最后会调用MapOutputTracker.registerShuffle
方法注册当前的shuffle,用于保存ShuffleMapStage上所有分区的MapStatus。
def registerShuffle(shuffleId: Int, numMaps: Int) {
if (mapStatuses.put(shuffleId, new Array[MapStatus](numMaps)).isDefined) {
throw new IllegalArgumentException("Shuffle ID " + shuffleId + " registered twice")
}
// add in advance
shuffleIdLocks.putIfAbsent(shuffleId, new Object())
}
注册Map输出
当某个ShuffleMapStage运行完成,那么会调用MapOutputTracker.registerMapOutputs
方法,将当前ShuffleMapStage中每个分区的计算结果(这些结果并不是真实的数据,而是这些数据所在的位置、大小等元数据信息)进行保存,并增加纪元号。这样依赖该ShuffleMapStage的其他ShuffleMapStage或ResultStage就可以通过这些元数据信息获取其需要的数据。
def registerMapOutputs(shuffleId: Int, statuses: Array[MapStatus], changeEpoch: Boolean = false) {
mapStatuses.put(shuffleId, statuses.clone())
if (changeEpoch) {
incrementEpoch()
}
}
如果执行器自己存储block(没有开启外部shuffle服务),并且如果执行器丢失导致所有相关的所有shuffle blocks丢失、或者执行器所在slave丢失,或者其他原因导致FetchFailed发生,在这种情况下,我们会假定所有与执行器相关的数据都已经丢失。这时就会移除当前Stage与此执行器相关的所有输出,并调用MapOutputTracker.registerMapOutputs
方法更新当前ShuffleMapStage相关的信息。
获取Map输出状态
在BlockStoreShuffleReader中,会调用mapOutputTracker.getMapSizesByExecutorId
方法获取一组二元组序列Seq[(BlockManagerId, Seq[(BlockId, Long)])]
,第一项代表了BlockManagerId,第二项描述了存储于该BlockManager上的一组shuffle blocks。
- 首先查看本地缓存中是否有shuffle数据,如果没有则从远程拉取;
- 数据结构
fetching
存储了当前正在fetch的shuffleId,如果fetching列表中包含了需要获取的shuffle,那么当前线程阻塞等待;否则,将当前的shuffle加入到fetching列表中。 - 接着调用
askTracker
方法,向MapOutputTrackerMaster发送GetMapOutputStatuses消息,并阻塞等待结果。 - MapOutputTrackerMaster接收到该消息后,会调用
getSerializedMapOutputStatuses
方法,查询本地记录shuffle对应的Map输出状态。
- 在获取的过程中需要为每个shuffleId分配一个分段锁,因为这里支持并发调用,同一时间有多个线程需要获取同一个shuffleId对应的输出,所以需要保证Map元数据信息只序列化或者广播一次。所以在获取锁之前和得到锁之后都需要再次查询一下缓存,可能有其他线程已经缓存了MapStatus。
- 如果缓存还是为空,则需要将MapStatus序列化或者包装为Broadcast。对于序列化还是广播,通过比较序列化后的结果大小是否超出
spark.shuffle.mapOutput.minSizeForBroadcast
,默认值为512K。 - 序列化完成后,将此结果进行缓存,并向MapOutputTrackerWorker返回结果。
- MapOutputTrackerWorker的
askTracker
接收到返回的结果后结束阻塞,将数据反序列化并返回。 - 最后根据执行的分区范围[startPartition, endPartition]将返回的结果Array[MapStatus]转换成Seq[(BlockManagerId, Seq[(BlockId, Long)])]。