Spark原始码系列(六)Shuffle的过程解析
问题导读: 1、shuffle过程的划分?
2、shuffle的中间结果如何存储?
3、shuffle的数据如何拉取过来?
![]() Shuffle过程的划分 Spark的操作模型是基于RDD的,当调用RDD的reduceByKey、groupByKey等类似的操作的时候,就需要有shuffle了。再拿出reduceByKey这个来讲。
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)] = { reduceByKey的时候,我们可以手动设定reduce的个数,如果不指定的话,就可能不受控制了。
def defaultPartitioner(rdd: RDD[_], others: RDD[_]*): Partitioner = { 如果不指定reduce个数的话,就按默认的走:
1、如果自定义了分区函数partitioner的话,就按你的分区函数来走。
2、如果没有定义,那么如果设置了spark.default.parallelism,就使用哈希的分区方式,reduce个数就是设置的这个值。
3、如果这个也没设置,那就按照输入数据的分片的数量来设定。如果是hadoop的输入数据的话,这个就多了。。。大家可要小心啊。
设定完之后,它会做三件事情,也就是之前讲的3次RDD转换。
//map端先按照key合并一次 ![]() 1、在第一个MapPartitionsRDD这里先做一次map端的聚合操作。
2、SHuffledRDD主要是做从这个抓取数据的工作。
3、第二个MapPartitionsRDD把抓取过来的数据再次进行聚合操作。
4、步骤1和步骤3都会涉及到spill的过程。
怎么做的聚合操作,回去看RDD那章。
Shuffle的中间结果如何存储 作业提交的时候,DAGScheduler会把Shuffle的过程切分成map和reduce两个Stage(之前一直被我叫做shuffle前和shuffle后),具体的切分的位置在上图的虚线处。
map端的任务会作为一个ShuffleMapTask提交,最后在TaskRunner里面调用了它的runTask方法。
override def runTask(context: TaskContext): MapStatus = { 遍历每一个记录,通过它的key来确定它的bucketId,再通过这个bucket的writer写入数据。
下面我们看看ShuffleBlockManager的forMapTask方法吧。
def forMapTask(shuffleId: Int, mapId: Int, numBuckets: Int, serializer: Serializer) = { 1、map的中间结果是写入到本地硬盘的,而不是内存。
2、默认是一个map的中间结果文件是M*R(M=map数量,R=reduce的数量),设置了spark.shuffle.consolidateFiles为true之后是R个文件,根据bucketId把要分到同一个reduce的结果写入到一个文件中。
3、consolidateFiles采用的是一个reduce一个文件,它还记录了每个map的写入起始位置,所以查找的时候,先通过reduceId查找到哪个文件,再同坐mapId查找索引当中的起始位置offset,长度length=(mapId + 1).offset -(mapId).offset,这样就可以确定一个FileSegment(file, offset, length)。
4、Finally,存储结束之后, 返回了一个new MapStatus(blockManager.blockManagerId, compressedSizes),把blockManagerId和block的大小都一起返回。
个人想法,shuffle这块和hadoop的机制差别不大,tez这样的引擎会赶上spark的速度呢?还是让我们拭目以待吧!
Shuffle的数据如何拉取过来 case smt: ShuffleMapTask => 1、把结果添加到Stage的outputLocs数组里,它是按照数据的分区Id来存储映射关系的partitionId->MapStaus。
2、stage结束之后,通过mapOutputTracker的registerMapOutputs方法,把此次shuffle的结果outputLocs记录到mapOutputTracker里面。
这个stage结束之后,就到ShuffleRDD运行了,我们看一下它的compute函数。
SparkEnv.get.shuffleFetcher.fetch[P](shuffledId, split.index, context, ser) 它是通过ShuffleFetch的fetch方法来抓取的,具体实现在BlockStoreShuffleFetcher里面。
override def fetch[T]( 1、MapOutputTrackerWorker向MapOutputTrackerMaster获取shuffle相关的map结果信息。
2、把map结果信息构造成BlockManagerId --> Array(BlockId, size)的映射关系。
3、通过BlockManager的getMultiple批量拉取block。
4、返回一个可遍历的Iterator接口,并更新相关的监控参数。
我们继续看getMultiple方法。
def getMultiple( 分两种情况处理,分别是netty的和Basic的,Basic的就不讲了,就是通过ConnectionManager去指定的BlockManager那里获取数据,上一章刚好说了。
我们讲一下Netty的吧,这个是需要设置的才能启用的,不知道性能会不会好一些呢?
看NettyBlockFetcherIterator的initialize方法,再看BasicBlockFetcherIterator的initialize方法,发现Basic的不能同时抓取超过48Mb的数据。
override def initialize() { 在NettyBlockFetcherIterator的sendRequest方法里面,发现它是通过ShuffleCopier来试下的。
val cpier = new ShuffleCopier(blockManager.conf) 这块接下来就是netty的客户端调用的方法了,我对这个不了解。在服务端的处理是在DiskBlockManager内部启动了一个ShuffleSender的服务,最终的业务处理逻辑是在FileServerHandler。
它是通过getBlockLocation返回一个FileSegment,下面这段代码是ShuffleBlockManager的getBlockLocation方法。
def getBlockLocation(id: ShuffleBlockId): FileSegment = { 先通过shuffleId找到ShuffleState,再通过reduceId找到文件,最后通过mapId确定它的文件分片的位置。但是这里有个疑问了,如果启用了consolidateFiles,一个reduce的所需数据都在一个文件里,是不是就可以把整个文件一起返回呢,而不是通过N个map来多次读取?还是害怕一次发送一个大文件容易失败?这就不得而知了。 |
Spark原始码系列(六)Shuffle的过程解析的更多相关文章
- Spark原始码系列(五)分布式缓存
问题导读:spark缓存是如何实现的?BlockManager与BlockManagerMaster的关系是什么? 这个persist方法是在RDD里面的,所以我们直接打开RDD这个类. def pe ...
- Spark源码系列:RDD repartition、coalesce 对比
在上一篇文章中 Spark源码系列:DataFrame repartition.coalesce 对比 对DataFrame的repartition.coalesce进行了对比,在这篇文章中,将会对R ...
- Spark源码系列(六)Shuffle的过程解析
Spark大会上,所有的演讲嘉宾都认为shuffle是最影响性能的地方,但是又无可奈何.之前去百度面试hadoop的时候,也被问到了这个问题,直接回答了不知道. 这篇文章主要是沿着下面几个问题来开展: ...
- Spark 源码系列(六)Shuffle 的过程解析
Spark 大会上,所有的演讲嘉宾都认为 shuffle 是最影响性能的地方,但是又无可奈何.之前去百度面试 hadoop 的时候,也被问到了这个问题,直接回答了不知道. 这篇文章主要是沿着下面几个问 ...
- Spark 源码分析 -- task实际执行过程
Spark源码分析 – SparkContext 中的例子, 只分析到sc.runJob 那么最终是怎么执行的? 通过DAGScheduler切分成Stage, 封装成taskset, 提交给Task ...
- Spark源码系列(一)spark-submit提交作业过程
前言 折腾了很久,终于开始学习Spark的源码了,第一篇我打算讲一下Spark作业的提交过程. 这个是Spark的App运行图,它通过一个Driver来和集群通信,集群负责作业的分配.今天我要讲的是如 ...
- Spark源码系列(九)Spark SQL初体验之解析过程详解
好久没更新博客了,之前学了一些R语言和机器学习的内容,做了一些笔记,之后也会放到博客上面来给大家共享.一个月前就打算更新Spark Sql的内容了,因为一些别的事情耽误了,今天就简单写点,Spark1 ...
- Spark源码系列(三)作业运行过程
作业执行 上一章讲了RDD的转换,但是没讲作业的运行,它和Driver Program的关系是啥,和RDD的关系是啥? 官方给的例子里面,一执行collect方法就能出结果,那我们就从collect开 ...
- Spark源码系列(五)分布式缓存
这一章想讲一下Spark的缓存是如何实现的.这个persist方法是在RDD里面的,所以我们直接打开RDD这个类. def persist(newLevel: StorageLevel): this. ...
随机推荐
- D:Sequence Swapping
BaoBao has just found a strange sequence {<, >, <, >, , <, >} of length in his poc ...
- Flutter 使用Navigator进行局部跳转页面
老孟导读:Navigator组件使用的频率不是很高,但在一些场景下非常适用,比如局部表单多页填写.底部导航一直存在,每个tab各自导航场景. Navigator 是管理路由的控件,通常情况下直接使用N ...
- DQN(Deep Q-learning)入门教程(二)之最优选择
在上一篇博客:DQN(Deep Q-learning)入门教程(一)之强化学习介绍中有三个很重要的函数: 策略:\(\pi(a|s) = P(A_t=a | S_t=s)\) 状态价值函数:\(v_\ ...
- eatwhatApp开发实战(十三)
这次内容,我们就项目中添加商店名称的EditText进行修改,让添加按钮随着edittext的内容而改变. 上代码,首先是xml文件上对两个控件的修改: <RelativeLayout andr ...
- sqlmap tamper脚本备忘录与tamper脚本编写
查看sqlmap全部脚本 $ python sqlmap.py --list-tampers 使用方法 --tamper=TAMPER 2019.9更新后翻译 * apostrophemask.py- ...
- data类型的url
所谓"data"类型的Url格式,是在RFC2397中 提出的,目的对于一些"小"的数据,可以在网页中直接嵌入,而不是从外部文件载入.例如对于img这个Ta ...
- Java实现 洛谷 P1060 开心的金明
题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:"你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过NN元钱 ...
- (Java实现) 洛谷 P1098 字符串的展开
import java.util.ArrayList; import java.util.Scanner; public class zifuchuandezhankai { static Array ...
- Java实现 LeetCode 213 打家劫舍 II(二)
213. 打家劫舍 II 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金.这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的.同时,相邻的房屋装有相互连通的防盗 ...
- Java实现 蓝桥杯 算法提高 数组求和
试题 算法提高 数组求和 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 输入n个数,围成一圈,求连续m(m<n)个数的和最大为多少? 输入格式 输入的第一行包含两个整数n, ...