Spark Shuffle 源码剖析
概念理论铺垫
一、 Spark 分区数量由谁决定
Spark source 如果是TextFile() 读取HDFS中的文件,2参数,第一个参数是路径,第二个是指定分区数量
- 如果指定分区数量,0或1,则分区数量的多少取决于文件数量的多少
- 如果没有指定分区数量,默认则是2,如果文件总大小为100m,100/2(分区数量)=50,50为goalSize,如果50会和Hdfs中设置得block块的大小比较,选出一个最小的值,然后最小的值的1.1倍不会参与切分,如果大于,则会进行while循环拆分,100-50=50,这里则会有2个切片,会有2个分区数量,也就是2个Task。
二 、Spark shuffle 理论知识铺垫
Spark Shuffle 有2个阶段,分别为Map和Reduce阶段,也可以是ShuffleWrite 阶段和 Shuffle Read 阶段,期间一定伴随着写磁盘的操作,先是缓存到内存中然后再溢写到磁盘当中,其中涉及到Spark内存管理机制,有兴趣可以去看以看。
大体流程 ShuffleMapStage -> Shuffle -> ResultStage
DAGScheduler 中 有 ShuffleMapStage ===== ResultStage
进去ResultTask中没有发现run方法 但是看到他的父类是Task
父类中的Run方法被Finall 修饰 ,但是还是调用了 RunTask() 方法 ,这种写法叫做模板方法模式,给你一个骨架,让你自己去 写实现,具体怎么实现任务的运行由你自己决定!
然后又回到了runTask() 方法当中 ,其中有一个func(context, rdd.iterator(partition, context)) iterator 迭代器,点进去
final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
// 先判断存储等级 如果不是none 会进行获取并计算
if (storageLevel != StorageLevel.NONE) {
getOrCompute(split, context)
} else {
computeOrReadCheckpoint(split, context)
}
}
点获取并计算,获取并进行计算,点进去
computeOrReadCheckpoint(partition, context) // 进行计算 并做ck
private[spark] def computeOrReadCheckpoint(split: Partition, context: TaskContext): Iterator[T] =
{
if (isCheckpointedAndMaterialized) {
firstParent[T].iterator(split, context)
} else {
compute(split, context) // 计算 点进去是一个抽象方法 所以看子类 子类重写方法的实现
}
}
找ShuffleRDD
override def compute(split: Partition, context: TaskContext): Iterator[(K, C)] = {
val dep = dependencies.head.asInstanceOf[ShuffleDependency[K, V, C]]
SparkEnv.get.shuffleManager.getReader(dep.shuffleHandle, split.index, split.index + 1, context)
.read() // 进行读操作
.asInstanceOf[Iterator[(K, C)]]
}
Shuffle Write 阶段
ShuffleMapTask -> runTask -> Write -> ShuffleWrite -> Write-> SortShuffleWrite->writeIndexFileAndCommit-> DataFile IndexFile
如果ShuffleMapStage 前面还有阶段ShuffleMapStage 则是 writer.write(rdd.iterator(partition, context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]]) 中的iterator 进行读操作 !
Spark 中沿用了分段日志 来解决数据寻址效率 ! 在Kafka中也是采用的是分段日志 来存储日志 分为 Segment 其中最重要的是 索引文件 ,时间戳索引文件,数据文件等 当然也含有 .clean、.deleted 结尾的文件 存在 解决了kafka 找数据时的速度 !!
在此补充kafka 高性能之道
Kafka 高性能之道
- 顺序写和Memeory map file (提高写入性能)
因为kafka是基于磁盘,但是kafka能够轻松应对每秒百万极的写入,其中用到的就是顺序写和MMFile
顺序写: 分段日志 !!!
因为硬盘是机械结构,每次读写都会寻址,然后写入,其中寻址是一个机械动作
,他是最耗时的,所以硬盘最讨厌随机IO,kafka利用顺序写,解决了大部分的寻址时间,因为kafka是基于内存的,所以不能和内存比较,所以他的数据写入时有一定的时间的。
MMFile:
kafka充分利用了现代操作系统分页存储来利用内存提高IO效率,MMFile叫做内存映射文件,在64位操作系统当中一般可以标是20g的数据文件,他的工作原理就时直接利用操作系统的page来实现文件到物理内存的直接映射,完成MMF后,用户对内存的所有操作会被操作系统自动刷新磁盘当中,来提高IO效率。
用户空间
运行在操作系统之上
内核空间
运行在操作系统之下
一般数据写入 除了顺序IO写入之外,伴随的是内存映射文件,将文件交给你pageCach,由操作系统自己决定何时刷新到磁盘当中
ZeroCopy:
kafka在响应客户端进行读出的时候,底层时采用的时0拷贝来实现,直接通过内核空间,传递输出,数据没有到达用户空间。
geCach,由操作系统自己决定何时刷新到磁盘当中
ZeroCopy:
kafka在响应客户端进行读出的时候,底层时采用的时0拷贝来实现,直接通过内核空间,传递输出,数据没有到达用户空间。
读请求 -> cpu -> 磁盘将数据读到自己的控制缓存区 -> 将磁盘缓冲区缓存到内核缓冲区当中 -> 将数据写出