ShuffleMapTask的结果(ShuffleMapStage中FinalRDD的数据)都将写入磁盘,以供后续Stage拉取,即整个Shuffle包括前Stage的Shuffle Write和后Stage的Shuffle Read。
Shuffle Write
概述:
- 写records到内存缓冲区(一个数组维护的map),每次insert&update都需要检查是否达到溢写条件
- 若需要溢写,将集合中的数据根据partitionId和key(若需要)排序后顺序溢写到一个临时的磁盘文件,并释放内存新建一个map放数据,每次溢写都是写一个新的临时文件
- 一个task最终对应一个文件,将还在内存中的数据和已经spill的文件根据reduce端的partitionId进行合并,合并后需要再次聚合排序(有需要情况下),再根据partition的顺序写入最终文件,并返回每个partition在文件中的偏移量,最后以MapStatus对象返回给driver并注册到MapOutputTrackerMaster中,后续reduce好通过它来访问
入口:
执行一个ShuffleMapTask最终的执行逻辑是调用了ShuffleMapTask类的runTask()方法:其中的finalRDD和dependency是在Driver端DAGScheluer中提交Stage的时候加入广播变量的。
接着通过SparkEnv获取shuffleManager,默认使用的是sort(对应的是org.apache.spark.shuffle.sort.SortShuffleManager),可通过spark.shuffle.manager设置。然后调用了manager.getWriter方法,getWriter返回的是SortShuffleWriter,直接看writer.write发生了什么:
- 通过判断是否有map端的combine来创建不同的ExternalSorter,若有则将对应的aggregator和keyOrdering作为参数传入
- 调用sorter.insertAll(records),将records写入内存缓冲区,超过阈值则溢写到磁盘文件
- Merge内存记录和所有被spill到磁盘的文件,并写到最终的数据文件.data中
- 将每个partition的偏移量写到index文件中。
先细看sorter.inster是怎么写到内存,并spill到磁盘文件的:
- 需要聚合的情况,遍历records拿到record的KV,通过map的changeValue方法并根据update函数来对相同K的V进行聚合,这里的map是PartitionedAppendOnlyMap类型,只能添加数据不能删除数据,底层实现是一个数组,数组中存KV键值对的方式是[K1,V1,K2,V2…],每一次操作后都会判断是否要spill到磁盘
- 不需要聚合的情况,直接将record放入buffer,然后判断是否要溢写到磁盘
先看map.changeValue方法到底是怎么通过map实现对数据combine的:
override def changeValue(key: K, updateFunc: (Boolean, V) => V): V = {
// 通过聚合算法得到newValue
val newValue = super.changeValue(key, updateFunc)
// 跟新对map的大小采样
super.afterUpdate()
newValue
}
super.changeValue的实现:
根据K的hashCode再哈希与上掩码 得到 pos,2 * pos 为 k 应该所在的位置,2 * pos + 1 为 k 对应的 v 所在的位置,获取k应该所在位置的原来的key:
- 若原来的key和当前的 k 相等,则通过update函数将两个v进行聚合并更新该位置的value
- 若原来的key存在但不和当前的k 相等,则说明hash冲突了,更新pos继续遍历
- 若原来的key不存在,则将当前k作为该位置的key,并通过update函数初始化该k对应的聚合结果,接着会通过incrementSize()方法进行扩容。更新curSize,若当前大小超过了阈值growThreshold(growThreshold是当前容量capacity的0.7倍),

最低0.47元/天 解锁文章
1258

被折叠的 条评论
为什么被折叠?



