窄依赖和宽依赖
窄依赖
- 每一个父RDD的Partition最多被子RDD的一个Partition使用
- 独生子女:一个爹RDD只有一个子
宽依赖
- 每一个父RDD的Partition被子RDD的多个Partition使用,伴随shuffle
- 超生:一个爹RDD有多个子
个人理解
由于还没学shuffle,所以从宏观简单思考。学一个东西不能死记硬背,最好的理解就是:问问自己为啥要分窄依赖和宽依赖?
先分析例子:
- 例1:用map时,一个分区里的数据经过函数,形成新的数据,大家你搞你的我搞我的,互不干扰。
- 例2:用合并操作时,多个分区合到一个分区,同样,各走各的,顶走跑之前计算下新偏移量(这个偏移别人没跑完我也知道),也可以说是互不干扰
- 例3:用groupbykey时,这下可不是互不干扰了,因为需要比较洗牌,你得等你的伙伴(另一个分区)算完了,才能执行groupbykey。
因此我觉得这就是所谓的宽依赖:别的分区没跑完,不能执行下一步,需要等待。只有当大家都准备好了,才可以一起进行洗牌。由于分区里的数据顺序之前是乱的,所以shuffle时一般都会拆开,然后送到不同的子分区。这就造成了结果——超生。说实话,如果你从结果出发去思考,是不好区分例2例3的。
接着,划分窄依赖(别的分区没跑完,可以执行下一步)和宽依赖(别的分区没跑完,不可以执行下一步)的原因显而易见。我们可以把窄依赖的步骤划分到一起,它可以一路执行,不需要等待,直到宽依赖步骤卡住(必须等其它分区执行完)。这个从窄依赖一路执行到宽依赖的过程,可以在逻辑上划分成一个stage。这也就是常说的宽依赖是划分Stage的依据。
任务划分
RDD任务的切分,分为:Application、Job、Stage和Task,而且每一层都是1对n的关系
4个名词
- Application:初始化一个SparkContext即生成一个Application
- Job:一个Action算子就会生成一个Job
- Stage:根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage。
- Task:Stage是一个TaskSet,将Stage划分的结果发送到不同的Executor执行即为一个Task。
个人理解
同样思考为啥要划分这么多东西?
- Application
一个spark不止跑一个程序吧,所以一个程序一个 Application理所当然,进而生成一个AppMaster管理它。
- Job
一个程序有许多转换算子和行动算子。只有执行到行动操作才真正改变数据,所以把截止到行动算子的算子划一个job合情合理吧。而且我们从源码也可以看到,执行一个行动操作,就会执行sc.runJob(...)
- Stage
在一个Job中,有的可一路执行到宽依赖的,不需要等待,按这个划分为一个Stage。这个不理解的再看看上面的分析。
- Task
在一个Stage中,我们观察最后一组分区,也就是shuffer前的,由于到这里都是可以一路执行的,所以按最后一组分区的个数,一个分区划一个Task。此时都划到分区了,自然不用划分了。