在0.19.0以前的版本中,Hadoop自身并没有提供全排序的solution,如果使用缺省的 partitioner(HashPartitioner)每个reducer的输出自身是有序的,但是多个reducer的输出文件之间不存在全序的关 系;如果想实现全排序,需要自己实现Partitioner,比如针对key为Mac地址的Partitioner,如假定Mac地址的分布是均匀的,可 以根据Mac地址的前两个字节构造不超过255个reducer的Partitioner;但是这种Partitoiner是应用逻辑相关的,因此没有通 用性,为此Hadoop 0.19.0提供了一个通用的全序Partitioner。
TotalOrderPartitioner最初用于Hadoop Terasort ,也许是考虑到其通用性,后来作为0.19.0的release feature发布。
Partitioner的目的是决定每一个Map输出的Record由哪个Reducer来处理,它必须尽可能满足
1. 平均分布。即每个Reducer处理的Record数量应该尽可能相等。
2. 高效。由于每个Record在Map Reduce过程中都需要由Partitioner分配,它的效率至关重要,需要使用高效的算法实现。
获取数据的分布
对于第一点,由于TotalOrderPartitioner 事先并不知道key的分布,因此需要通过少量数据sample估算key的分布,然后根据分布构造针对的Partition模型。
0.19.0中有一个InputSampler就是做这个事情的, 通过指定Reducer个数, 并读取一部分的输入数据作为sample,将sample数据排序并根据Reducer个数等分后,得到每个Reducer处理的区间。比如包含9条数据的sample,其排好序的key分别为:
a b c d e f g h i
如果指定Reducer个数为3,每个Reducer对应的区间为
Reducer0 [a, b, c]
Reducer1 [d, e, f]
Reducer2 [g, h, i]
区间之间的边界称为Cut Point ,上面三个Reducer的Cut point为 d, g 。 InputSampler将这cut points排序并写入HDFS文件,这个文件即包含了输入数据的分布规律。
根据分布构建高效Partition模型
对于上面提到的第2点,高效性, 在读取数据的分布规律文件之后,TotalOrderPartitioner会判断key是不是BinaryComparable类型的。
BinaryComparable的含义是“字节可比的”,o.a.h.io.Text就是一个这样的类型,因为两个Text对象可以按字节比较,如果对应的字节不相等就立刻可以判断两个Text的大小。
先说不是 BinaryComparable 类型的情况,这时 TotalOrderPartitioner会使用二分查找BinarySearch来确定key属于哪个区间,进而确定属于哪个Reducer,每一次查找的时间复杂度为O(logR),R为Reducer的个数。
如果key是 BinaryComparable类型, TotalOrderPartitioner会根据 cut points构造Trie 。 Trie 是一种更为高效的用于查找的数据结构, 这种数据结构适合key为字符串类型,如下图
可以看到,使用 Trie进行Partition的效率高于binarySearch,单次执行两种查找可能不会有 什么感觉,但是当处理亿计的Record时,他们的差距就明显了。