由于继承Mapper基类的Map阶段类和继承Reducer基类的Reduce阶段类的运行都是独立的,并不像代码看起来那样会共享同一个Java虚拟机的资源,所以不能直接使用代码级别的全局变量。下面介绍几种在MapReduce编程中相对有效的设置全局变量的方法。
1、读写HDFS文件
在MapReduce框架中,Map task和Reduce task都运行在Hadoop集群的节点上,所以它们可以通过读写HDFS中预定好的同一个文件来实现全局共享数据。
这种方法的优点是能够实现读写,也比较直观;而缺点是要使用IO,这将占用系统资源,增加作业完成的资源消耗。
2、配置Job属性
在MapReduce执行过程中,task可以读取Job的属性。基于这个特性,大家可以在任务启动之初将一些简单的全局数据封装到作业的配置属性中,然后在task中再获取配置属性中的全局数据。
这种方法的优点是简单,资源消耗小;缺点是对量比较大的共享数据显得无力。
3、使用DistributedCache
DistributedCache是MapReduce为应用提供缓存文件的只读工具,它可以缓存文本文件、压缩文件和jar文件等。在使用时,用户在作业配置时使用本地或HDFS文件的URL来将其设置成共享缓存文件。在作业启动之后和task启动之前,MapReduce框架会将可能需要的缓存文件复制到执行任务节点的本地。
这种方法的优点是每个Job共享文件只会在启动之后复制一次,并且它适合用于大量的共享数据。
程序场景
过滤无意义单词之后的文本词频统计。具体做法是:将事先定义的无意义单词保存成文件,保存到HDFS上,然后在程序中将这个文件定义成作业的缓冲文件。在Map启动后先读入缓冲文件,然后统计过滤后单词的频数。
- 将要缓存的文件复制到HDFS上。
- 启用作业属性配置,并设置待缓存文件。
- 在Map函数中使用DistributedCache。
详细代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.util.HashSet;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class AdvancedWordCount {
public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
private HashSet<String> keyWord;
private Path[] localFiles;
public void setup(Context context) throws IOException, InterrruptedException {
keyWord = new HashSet<String>();
Configuration conf = context.getConfiguration();
localFiles = DistributedCache.getLocalCacheFiles(conf);
for (int i=0; i<localFiles.length; i++) {
String a;
BufferedReader br = new BufferedReader(new FileReader(
localFiles[i].toString()));
while (a = br.readLine() != null) {
keyWord.add(a);
}
br.close();
}
}
public void map(Object key, Text value, Context context) throws
IOException, InterrruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
String aword = itr.nextToken();
if (keyWord.contains(aword) == true)
continue;
word.set(aword);
context.write(word, one);
}
}
}
public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterator<IntWritable> values, Context context)
throws IOException, InterrruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
DistributedCache.addCacheFile(new URI("hdfs://localhost:9000/user/
ubuntu/cachefile/KeyWord#KeyWord"), conf);
Job job = new Job(conf, "word count");
Job.setJarByClass(AdvancedWordCount.class);
Job.setMapperClass(TokenizerMapper.class);
Job.setCombinerClass(IntSumReducer.class);
Job.setReducerClass(IntSumReducer.class);
Job.setOutputKeyClass(Text.class);
Job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path("input"));
FileOutputFormat.setOutputPath(job, new Path("output"));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}