对一个很大的整数数组进行排序
简单分治
通常遇到很大的整数数组,都是不能直接放入道内存中的。比较直观的思路是借鉴归并排序的方式分而治之。将数组进行切分,直到数组的大小是可以接受的范围。我们对数组直接进行排序,然后再一步一步的回退进行合并。
但是这种方式,在最后的合并阶段,通常还是需要把大量的数值读入道内存中进行比较的。且这种全局有序性的排序(order by)最后都会把任务丢到一个节点上去做合并,该节点压力会很大。因此这种方式一般不被采用。
如何优化
还是回到分治的思路上,没法子太大了只能拆分。如果我们确保分区之间也是有序的,就可以简单化后续的合并(partition0 > partition1 > partition2 ......)。那么我们如何对数组进行分组呢?
如果我们采用hash的方式进行分区,可能会带来两个问题:破坏了分区间的有序性和可能会发生数据倾斜。
分区间有序
为了实现分区间有序,我们需要指定分区间的barrier,在barrier之中的,才被分到该分区。例如:[0-10000),[10000-20000)....采用这样的方式我们可以保证对所有分区排序完成后,分区之间也是有序的,即我们简单拼接便可以完成整体排序。但是这样划分无法避免数据倾斜的问题。
均分数据
提到数据倾斜,肯定就有小伙伴说这就好办了,增加随机前缀或者后缀,然后重新散列就行了。但是在这里是不可以的,因为前缀或后缀会破坏分区间的有序性。注意,当我们分区完成后,在排序之前,分区中的数据也是无序的。这样我们无法对该倾斜分区在保证分区间有序的前提下进行再拆分。这时候我们应该怎么办呢?
最后的方案
其实我们的目标就是让我们的数据尽可能的均分在分区之中,我们可以通过调整barrier的范围来完成。例如我们在10000-15000的数据量比较大,我们可以调整barrier到[0-80000),[80000-120000),[120000,160000)...从而将数据尽可能的均分。如果我们知道了大致的数据分布,我们就可以较好的处理划分的问题。所以我们可以先对数据进行采样,然后我们对采样的数据进行排序,再根据采样的结果来作为我们划分barrier的依据。通过这种办法我们可以较好的避免数据倾斜。