在上一个实验中
一个MapReduce 程序示例 细节决定成败(七) :自定义Key 及RecordReader,使用自定义的RecordReader 从split 中读取信息,封装到自定义的key中。
这一步在使用TotalOrderPartitioner 进行词频统计是非常重要的。
下面进入主题:在
一个MapReduce 程序示例 细节决定成败(五) :Partitioner 中,演示了如何使用一下自定义Partitioner。
我们使用了一个自定义的Partitioner,来实现当设置3个reducer时输出字符的排序问题。为了简化问题,我使用在partitioner中直接进行判断。
这里面其实是有两个我们应该注意的问题。
1、在partitioner代码中写死了,只有3个reducer,那如果我们想要把reducer的个数设置成其它值,还需要改代码。
2、partitioner 的一个作用就是解决数据倾斜的问题,但我们上次写的那个partitioner不适用
那接下来我们介绍的 TotalOrderPartitioner 就完美解决了上述两个问题。
本次涉及到的内容有。
1、生成样本partition file
2、使用使用CacheFile功能把partition file 分发到各个map task节点。
3、配置使用TotalOrderPartitioner.
因为TotaOrderlPartitioner是已经封装到hadoop 系统中了,所以我们只要正确配置就可以了。
partition file :描述了每个字符应该进入到哪儿个reducer中的文件,功能就是上一篇中我们简化处理的 a~i 分到0, h~q 分到1,r~z 分到2。
partition file 不用我们手动去写,而且手动去写也很难平均分布各个reducer任务的负载。可以使用InputSampler.RandomSampler完成此项工作的。
-
/**
-
* freq:每个key 可以被抽样次数
-
* numSamples:总样本数
-
*/
-
InputSampler.RandomSampler(double freq, int numSamples);
-
-
/**
-
* freq:每个key 可以被抽样次数
-
* numSamples:总样本数
-
* maxSplitsSampled:定义最多从多少个splits 上进行抽样
-
*/
-
InputSampler.RandomSampler(double freq, int numSamples, int maxSplitsSampled);
-
默认抽样是在所有的split上进行的,当输入文件很大的时候,使用第一个方法进行抽样代价会很大。
当处理文件很大的时候,加上第三个参数maxSplitsSampled 限定最多处理多少个split。
freq 参数: 被抽样到的split中的每一个key 被选中的概率。 numSamples 代表抽样的总样本数。 比如从10000条数据中抽取 10条数,假如freq 设置为0.5,那按概率大约执行到20条的时候,样本数就够了。
但然后程序会继续执行,每个被抽中的样本去随机替换一条已经抽出的10条记录中的一条,一直到最后。
下面贴一下配置使用TotaOrderlPartitioner的代码
关注 getPartitionFile和 new URI 的写法。
-
job.setPartitionerClass(TotalOrderPartitioner.class);
-
job.setNumReduceTasks(3);
-
RandomSampler<MyKey, NullWritable> sampler
-
= new InputSampler.RandomSampler<MyKey,NullWritable>(0.6, 20, 3);
-
InputSampler.writePartitionFile(job, sampler);
-
-
String partitionFile = TotalOrderPartitioner.getPartitionFile(job.getConfiguration());
-
URI uri = new URI(partitionFile+"#"+TotalOrderPartitioner.DEFAULT_PATH);
-
job.addCacheFile(uri);
注意: 1、RecordReader的key与map输入输出key和reduce的输入key 要保持一致。 因为取样是从job 配置的InputFormat中得到RecordReader 然后不断的得到Key ,从中取样。
取到的样使用TotalOrderPartitioner 得到的partitionFile 是供分配到不同reducer的,所以RecordReader的key 要与Reduer的输入key 一致,又 RecordReader的key value 是输入到 map 中的,
Reduce 的输入又是Map的输出。绕了半天结论就是: RecordReader的key, Map 的输入输出key ,Reduce的输入key 是要保持一致的。
2、使用TotalOrderPartitioner前一定要配置了reduceTask 的个数、InputFormat、和上面讨论的 key class。
贴一下结果:
-
wordcount.MyKey@212cb585 8
-
wordcount.MyKey@212cb585 3
-
wordcount.MyKey@212cb585 4
-
wordcount.MyKey@212cb585 4
-
wordcount.MyKey@212cb585 11
-
wordcount.MyKey@212cb585 4
-
wordcount.MyKey@212cb585 3
-
wordcount.MyKey@168497f6 8
-
wordcount.MyKey@168497f6 5
-
wordcount.MyKey@168497f6 3
-
wordcount.MyKey@168497f6 3
-
wordcount.MyKey@168497f6 6
-
wordcount.MyKey@168497f6 7
-
wordcount.MyKey@168497f6 4
-
wordcount.MyKey@168497f6 12
-
wordcount.MyKey@168497f6 3
-
wordcount.MyKey@168497f6 3
-
wordcount.MyKey@2e67cd84 13
-
wordcount.MyKey@2e67cd84 4
-
wordcount.MyKey@2e67cd84 4
-
wordcount.MyKey@2e67cd84 6
-
wordcount.MyKey@2e67cd84 7
-
wordcount.MyKey@2e67cd84 3
-
wordcount.MyKey@2e67cd84 6
-
wordcount.MyKey@2e67cd84 3
注意到key的格式,这是由于我们的MyKey没有覆写toString 方法,输出的时候直接使用了Object的toString实现。
-
@Override
-
public String toString(){
-
return String.valueOf(this.c);
-
}
结果分析: 下面数据是运行结果,可以看到抽样划分的界限是a~e f~p q~z 。
-
[train@sandbox src]$ hdfs dfs -cat output/*0
-
a 8
-
b 3
-
c 4
-
d 4
-
e 11
-
[train@sandbox src]$ hdfs dfs -cat output/*1
-
f 4
-
g 3
-
h 8
-
i 5
-
j 3
-
k 3
-
l 6
-
m 7
-
n 4
-
o 12
-
p 3
-
[train@sandbox src]$ hdfs dfs -cat output/*2
-
q 3
-
r 13
-
s 4
-
t 4
-
u 6
-
w 7
-
x 3
-
y 6
-
z 3
-
下面贴出主程序代码
-
package wordcount;
-
-
import java.io.IOException;
-
import java.net.URI;
-
-
import org.apache.hadoop.conf.Configuration;
-
import org.apache.hadoop.conf.Configured;
-
import org.apache.hadoop.fs.Path;
-
import org.apache.hadoop.io.IntWritable;
-
import org.apache.hadoop.io.NullWritable;
-
import org.apache.hadoop.io.Text;
-
import org.apache.hadoop.mapred.lib.TotalOrderPartitioner;
-
import org.apache.hadoop.mapreduce.Job;
-
import org.apache.hadoop.mapreduce.Mapper;
-
import org.apache.hadoop.mapreduce.Reducer;
-
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
-
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
-
import org.apache.hadoop.mapreduce.lib.partition.InputSampler;
-
import org.apache.hadoop.mapreduce.lib.partition.InputSampler.RandomSampler;
-
import org.apache.hadoop.util.Tool;
-
import org.apache.hadoop.util.ToolRunner;
-
import org.apache.log4j.Logger;
-
-
public class MyWordCountJob extends Configured implements Tool {
-
Logger log = Logger.getLogger(MyWordCountJob.class);
-
-
public static class MyWordCountMapper extends
-
Mapper<MyKey, NullWritable, MyKey, IntWritable> {
-
Text mKey = new Text();
-
IntWritable mValue = new IntWritable(1);
-
@Override
-
protected void map(MyKey key, NullWritable value, Context context)
-
throws IOException, InterruptedException {
-
context.write(key, mValue);
-
}
-
}
-
-
public static class MyWordCountReducer extends Reducer<MyKey, IntWritable, MyKey, IntWritable> {
-
Text rkey = new Text();
-
IntWritable rvalue = new IntWritable(1);
-
@Override
-
protected void reduce(MyKey key, Iterable<IntWritable> values,Context context)
-
throws IOException, InterruptedException {
-
-
int n=0;
-
for(IntWritable value :values){
-
n+= value.get();
-
}
-
rvalue.set(n);
-
context.write(key, rvalue);
-
}
-
}
-
-
@Override
-
public int run(String[] args) throws Exception {
-
//valid the parameters
-
if(args.length !=2){
-
return -1;
-
}
-
-
Job job = Job.getInstance(getConf(), "MyWordCountJob");
-
job.setJarByClass(MyWordCountJob.class);
-
-
Path inPath = new Path(args[0]);
-
Path outPath = new Path(args[1]);
-
-
outPath.getFileSystem(getConf()).delete(outPath,true);
-
TextInputFormat.setInputPaths(job, inPath);
-
TextOutputFormat.setOutputPath(job, outPath);
-
-
-
job.setMapperClass(MyWordCountJob.MyWordCountMapper.class);
-
job.setReducerClass(MyWordCountJob.MyWordCountReducer.class);
-
-
job.setInputFormatClass(MyCombinedFilesInputFormat.class);
-
MyCombinedFilesInputFormat.setMaxInputSplitSize(job, 1024*1024*64);
-
job.setOutputFormatClass(TextOutputFormat.class);
-
job.setMapOutputKeyClass(MyKey.class);
-
job.setMapOutputValueClass(IntWritable.class);
-
job.setOutputKeyClass(MyKey.class);
-
job.setOutputValueClass(IntWritable.class);
-
-
job.setPartitionerClass(TotalOrderPartitioner.class);
-
job.setNumReduceTasks(3);
-
RandomSampler<MyKey, NullWritable> sampler
-
= new InputSampler.RandomSampler<MyKey,NullWritable>(0.6, 20, 3);
-
InputSampler.writePartitionFile(job, sampler);
-
-
String partitionFile = TotalOrderPartitioner.getPartitionFile(job.getConfiguration());
-
URI uri = new URI(partitionFile+"#"+TotalOrderPartitioner.DEFAULT_PATH);
-
job.addCacheFile(uri);
-
-
return job.waitForCompletion(true)?0:1;
-
}
-
public static void main(String [] args){
-
int result = 0;
-
try {
-
result = ToolRunner.run(new Configuration(), new MyWordCountJob(), args);
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
System.exit(result);
-
}
-
-
}
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/30066956/viewspace-2109331/
,如需转载,请注明出处,否则将追究法律责任。