Hadoop 的 TotalOrderPartitioner

http://blog.oddfoo.net/2011/04/17/mapreduce-partition%E5%88%86%E6%9E%90-2/ 

Partition所處的位置


patition類結構


1. Partitioner是partitioner的基類,如果需要定製partitioner也需要繼承該類。

2. HashPartitioner是mapreduce的默認partitioner。計算方法是

which reducer=(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks,得到當前的目的reducer。

3. BinaryPatitioner繼承於Partitioner< BinaryComparable ,V>,是Partitioner的偏特化子類。該類提供leftOffset和rightOffset,在計算which reducer時僅對鍵值K的[rightOffset,leftOffset]這個區間取hash。

Which reducer=(hash & Integer.MAX_VALUE) % numReduceTasks

4. KeyFieldBasedPartitioner也是基於hash的個partitioner。和BinaryPatitioner不同,它提供了多個區間用於計算hash。當區間數爲0時KeyFieldBasedPartitioner退化成HashPartitioner。

5. TotalOrderPartitioner這個類可以實現輸出的全排序。不同於以上3個partitioner,這個類並不是基於hash的。在下一節裏詳細的介紹totalorderpartitioner。

TotalOrderPartitioner


每一個reducer的輸出在默認的情況下都是有順序的,但是reducer之間在輸入是無序的情況下也是無序的。如果要實現輸出是全排序的那就會用到TotalOrderPartitioner。

要使用TotalOrderPartitioner,得給TotalOrderPartitioner提供一個partition file。這個文件要求Key (這些key就是所謂的劃分)的數量和當前reducer的數量-1相同並且是從小到大排列。對於爲什麼要用到這樣一個文件,以及這個文件的具體細節待會還會提到。

TotalOrderPartitioner對不同Key的數據類型提供了兩種方案:

1) 對於非BinaryComparable(參考附錄A)類型的Key,TotalOrderPartitioner採用二分發查找當前的K所在的index。

例如reducer的數量爲5,partition file 提供的4個劃分爲【2,4,6,8】。如果當前的一個key value pair 是<4,”good”>利用二分法查找到index=1,index+1=2那麼這個key value pair將會發送到第二個reducer。如果一個key value pair爲<4.5, “good”>那麼二分法查找將返回-3,同樣對-3加1然後取反就是這個key value pair 將要去的reducer。

對於一些數值型的數據來說,利用二分法查找複雜度是o(log (reducer count)),速度比較快。

2) 對於BinaryComparable類型的Key(也可以直接理解爲字符串)。字符串按照字典順序也是可以進行排序的。這樣的話也可以給定一些劃分,讓不同的字符串key分配到不同的reducer裏。這裏的處理和數值類型的比較相近。

例如reducer的數量爲5,partition file 提供了4個劃分爲【“abc”, “bce”, “eaa”, ”fhc”】那麼“ab”這個字符串將會被分配到第一個reducer裏,因爲它小於第一個劃分“abc”。

但是不同於數值型的數據,字符串的查找和比較不能按照數值型數據的比較方法。mapreducer採用的Tire tree的字符串查找方法。查找的時間複雜度o(m),m爲樹的深度,空間複雜度o(255^m-1)。是一個典型的空間換時間的案例。

Tire Tree


Tire tree的構建

假設樹的最大深度爲3,劃分爲【aaad ,aaaf, aaaeh,abbx 】

採樣類結構圖

採樣類結構圖

採樣方式對比表:

類名稱

採樣方式

構造方法

效率

特點

SplitSampler<K,V>

對前n個記錄進行採樣

採樣總數,劃分數

最高

 

RandomSampler<K,V>

遍歷所有數據,隨機採樣

採樣頻率,採樣總數,劃分數

最低

 

IntervalSampler<K,V>

固定間隔採樣

採樣頻率,劃分數

對有序的數據十分適用

writePartitionFile這個方法很關鍵,這個方法就是根據採樣類提供的樣本,首先進行排序,然後選定(隨機的方法)和reducer數目-1的樣本寫入到partition file。這樣經過採樣的數據生成的劃分,在每個劃分區間裏的key value pair 就近似相同了,這樣就能完成均衡負載的作用。

TotalOrderPartitioner實例


<pre java;="" auto-links:="" false;"="" style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word;">
複製代碼
public class SortByTemperatureUsingTotalOrderPartitioner extends Configured
        implements Tool
{
    @Override
    public int run(String[] args) throws Exception
    {
        JobConf conf = JobBuilder.parseInputAndOutput(this, getConf(), args);
        if (conf == null) {
            return -1;
        }
        conf.setInputFormat(SequenceFileInputFormat.class);
        conf.setOutputKeyClass(IntWritable.class);
        conf.setOutputFormat(SequenceFileOutputFormat.class);
        SequenceFileOutputFormat.setCompressOutput(conf, true);
        SequenceFileOutputFormat
                .setOutputCompressorClass(conf, GzipCodec.class);
        SequenceFileOutputFormat.setOutputCompressionType(conf,
                CompressionType.BLOCK);
        conf.setPartitionerClass(TotalOrderPartitioner.class);
        InputSampler.Sampler<IntWritable, Text> sampler = new InputSampler.RandomSampler<IntWritable, Text>(
                0.1, 10000, 10);
        Path input = FileInputFormat.getInputPaths(conf)[0];
        input = input.makeQualified(input.getFileSystem(conf));
        Path partitionFile = new Path(input, "_partitions");
        TotalOrderPartitioner.setPartitionFile(conf, partitionFile);
        InputSampler.writePartitionFile(conf, sampler);
        // Add to DistributedCache
        URI partitionUri = new URI(partitionFile.toString() + "#_partitions");
        DistributedCache.addCacheFile(partitionUri, conf);
        DistributedCache.createSymlink(conf);
        JobClient.runJob(conf);
        return 0;
    }

    public static void main(String[] args) throws Exception {
        int exitCode = ToolRunner.run(
                new SortByTemperatureUsingTotalOrderPartitioner(), args);
        System.exit(exitCode);
    }
}
複製代碼

示例程序引用於:http://www.cnblogs.com/funnydavid/archive/2010/11/24/1886974.html

附錄A
Text 爲BinaryComparable,WriteableComparable類型。
BooleanWritable、ByteWritable、DoubleWritable、MD5hash、IntWritable、FloatWritable、LongWritable、NullWriable等都爲WriteableComparable。

 

 

http://www.cnblogs.com/OnlyXP/archive/2008/12/06/1349026.html

 

在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構造TrieTrie是一種更爲高效的用於查找的數據結構,這種數據結構適合key爲字符串類型,如下圖

TotalOrderPartitioner中的Trie缺省深度爲2,即使用2+1個prefix構造Trie;每個父節點有255個子節點,對應255個ASCII字符。查找的時間複雜度爲O(m),m爲樹的深度,空間複雜度爲O(255m-1),可以看到,這是一種空間換時間的方案,當樹深度爲2時,可以最多分配255 * 255個reducer,這在絕大情況下足夠了。

可以看到,使用
Trie進行Partition的效率高於binarySearch,單次執行兩種查找可能不會有什麼感覺,但是當處理億計的Record時,他們的差距就明顯了。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章