一、为什么要看源码了解其原理呢?(可忽略)
因为项目中需要做排行榜,也就是需要排序,且给出对应排名。
搜索了不少资料,模模糊糊的貌似通过sortBy+zipWithIndex两个算子就能做到。但是就是不敢用。
第一:
不知道sortBy是怎么做到全局排序的,是否有性能问题,导致不太敢用。因为之前学习hadoop的mapreduce,以及hive,了解到全局排序会把所有数据都shuffle到一个reduce里面进行排序(当然,有优化方案)
如果数据量很大,spark也是通过这种方式实现全局排序的话,那spark一个task所需要的内存那会非常大,并且最终只有一个task会需要这么大的内存,那肯定不行。所以由于不清楚spark全局排序的原理,就不敢用,或者想自己实现归并排序。
第二:
不知道zipWithIndex的功能,主要是这个api的文档说的不清楚。
如图,圈起来的部分,这个说第一个分区的第一项索引为0,最后一个分区的最后一项为最大的索引。但是否有序?怎么个有序?在zipWithIndex之前执行了sortBy之后,是否会重新shufflue导致打乱顺序?
然后在圈起来的注释下一段又说和scala集合的zipWithIndex是一样的,这就让我觉得这个算子应该是我想像中的那样,会保证顺序,然后按顺序给每个元素递增索引。。
但这不能靠猜测,要么就用大规模数据测试一下,要么就翻源码。
我选择了后者,看源码。
二、原理
1、sortBy 原理
其实就是使用了RangePartitioner分区器
该分区器的实现方式主要是通过两个步骤来实现的,
(1)、先重整个RDD中抽取出样本数据,将样本数据排序,计算出每个分区的最大key值,形成一个Array[KEY]类型的数组变量rangeBounds;
(2)、判断key在rangeBounds中所处的范围,给出该key值在下一个RDD中的分区id下标
这样在分区号大的分区数据就比分区号小的分区数据大了,然后在各个分区内部再分别进行排序,这样的话就全局有序了(按分区号从小到大遍历数据,输出结果就是全局从小到大了)
2、zipWithIndex原理
(1)、计算每个分区的数量,然后计算每个分区的第一个元素在全局数据中的位置(index)
(2)、重写getPartitions函数,封装分区数据
正常RDD使用Partition对象存储分区号等信息,而当前zipWithIndexRDD又使用ZippedWithIndexRDDPartition封装进每个分区的第一个元素在全局数据的位置index。
(3)、重写compute函数,RDD会根据分区数n,调用n次该函数。该函数的作用就是重新封装每个分区数据的迭代器。
参数中,Partition对象又被传进来了,这个对象其实是上面getPartitions函数返回的n多个分区数据之一,也就是说,
这Partition对象其实是ZippedWithIndexRDDPartition类型的实例,所以可以强转类型,获取该分区的第一个元素在全局数据的位置index。
然后调用getIteratorZipWithIndex函数封装新的迭代器(给每个元素配上该元素在全局数据中的位置),其实就是从该分区一个元素的index开始累加,代码逻辑不复杂。