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;">
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
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時,他們的差距就明顯了。