前面已经介绍个几个MapReduce的例子,那个Hello world是最基础的,MapReduce Join篇写了怎么实现Map端和Reduce端的做法,还有个semi-join没有写出来,其实semi-join可以看做是两者的结合,所以没有做说明。MapReduce编程模型需要多写,多实践,毕竟多写笔下生花,只有遇到的坑多了,就没那么容易掉到坑里面,正所谓常在坑里走,哪有不被坑呢,。这不,咱们就以Hadoop里面的MapReduce examples为入口,多到坑里面走走看看。
WordCount,WordMean,WordMedian,WordStandardDeviation
WordCount不说了,直接跳过。先看看WordMean,里面的map和reduce方法也是很简单,和WordCount基本差不多,不过这里是以count和length两个字符作为key,记录每个单词的次数和长度,到了reduce的时候,根据key,把Iterable
count 5
length 23
分别读出count和length,然后将length/count就得到mean,然后打印出来。
在本地Eclipse运行的时候,遇到错误:
Exception in thread "main" DEBUG - stopping client from cache: org.apache.hadoop.ipc.Client@12998f87
java.lang.IllegalArgumentException: Wrong FS: hdfs://Hadoop-02:9000/output/part-r-00000, expected: file:///
at org.apache.hadoop.fs.FileSystem.checkPath(FileSystem.java:645)
at org.apache.hadoop.fs.RawLocalFileSystem.pathToFile(RawLocalFileSystem.java:80)
at org.apache.hadoop.fs.RawLocalFileSystem.deprecatedGetFileStatus(RawLocalFileSystem.java:529)
at org.apache.hadoop.fs.RawLocalFileSystem.getFileLinkStatusInternal(RawLocalFileSystem.java:747)
at org.apache.hadoop.fs.RawLocalFileSystem.getFileStatus(RawLocalFileSystem.java:524)
at org.apache.hadoop.fs.FilterFileSystem.getFileStatus(FilterFileSystem.java:409)
at org.apache.hadoop.fs.FileSystem.exists(FileSystem.java:1400)
at com.gl.test.WordMean.readAndCalcMean(WordMean.java:122)
at com.gl.test.WordMean.run(WordMean.java:186)
其实,reduce结果已经做完了,保存到hdfs,只是做readAndCalcMean出错。将代码稍作修改,便可以运行正常。
//FileSystem fs = FileSystem.get(conf);
Path file = new Path(path, "part-r-00000");
FileSystem fs = file.getFileSystem(conf);
WordMedian,这个和上面的也类似,我输入的文件是:
hello
world test
hello test
输出的结果很明显:
4 2
5 3
最后打印的结果是" The median is: 5"
这个例子里面有特别的地方是用到一个Counter(org.apache.hadoop.mapreduce.TaskCounter)
long totalWords = job.getCounters()
.getGroup(TaskCounter.class.getCanonicalName())
.findCounter("MAP_OUTPUT_RECORDS", "Map output records").getValue();
WordStandardDeviation,过程同上面的,唯一不同的是,这里的计算公式稍微多点,也不复杂,不过我还是看了半天。
Std_dev=sqrt([l1*l1+l2*l2...+ln*ln-n*mean*mean]/n) n为count,mean为上面计算的均值,这里标准差公式根号内除以n,没有使用n-1。
这个例子里面很重要的是使用了之前map计算的结果,count计算总数字个数,length计算总字符串长度,便于后面计算均值,square为每个长度的平方,也是为后面计算的公式前面部分用,这样在最后计算的时候,只需要用前面的结果来提高效率。
AggregateWordCount
这个例子关键是运行的时候加了这个一句:
Job job = ValueAggregatorJob.createValueAggregatorJob(args
, new Class[] {WordCountPlugInClass.class});
具体实现的时候是集成了ValueAggregatorBaseDescriptor,Mapreduce里面已经包含了各种数据类型的求和最大值,最小值的算法UniqValueCount,LongValueSum等等,本例就是LONG_VALUE_SUM
public static class WordCountPlugInClass extends
ValueAggregatorBaseDescriptor {
@Override
public ArrayList
Object val) {
String countType = LONG_VALUE_SUM;
ArrayList
String line = val.toString();
StringTokenizer itr = new StringTokenizer(line);
while (itr.hasMoreTokens()) {
Entry
if (e != null) {
retv.add(e);
}
}
return retv;
}
}
运行的时候,可以试试:
./bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.1.jar aggregatewordcount /input /wordtest 2 textinputformat
结果类似这样:(part-r-00000)
record_count 6
AggregateWordHistogram
这个例子也差不多,就是最后处理有些不同
public ArrayList
Object val) {
String words[] = val.toString().split(" |\t");
ArrayList
for (int i = 0; i < words.length; i++) {
Text valCount = new Text(words[i] + "\t" + "1");
Entry
valCount);
retv.add(en);
}
return retv;
}
运行下面的命令:
./bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.1.jar aggregatewordhist /input /wordtest 2 textinputformat
结果类似这样:record_count 3
本来examples里面带了一个Sort的例子,也很好,可是命令行直接运行还是有些麻烦,出了几次错误,里面的inFomat outKey等默认的参数格式比较难获得,生存那些格式的有些麻烦,这里还是自己仿照hello world写了个排序的,很简单,练练手。
我运行里面自带的例子的命令: ./bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.1.jar sort -inFormat org.apache.hadoop.mapreduce.lib.input.TextInputFormat -outKey org.apache.hadoop.io.Text -outValue org.apache.hadoop.io.Text /testdata /testresult
自己实现mapper和reducer,这里是利用mapreduce会对结果的key进行排序,这样刚好是我们想要的,唯一要注意的就是key的类型,是Text,IntWritable,还是其他自己定义的。这里我们用个IntWritable来排序。
public static class MyMapper extends Mapper
SecondarySort 二次排序的要求是按照第一字段排序之后,如果第一字段相同的,继续按照第二字段排序,当然不能破坏第一字段的排序。整个过程的基本思路是实现自定义格式的数据的排序,例子里面定义了一个IntPair实现了WritableComparable接口,提供了对字段的序列化,比较等方法。在mapper读取两个字段的数据之后,定义了一个PartitionerClass,主要依据第一个字段来Partitioner,这样保证第一字段相同的在同一个分区。分区函数类。这是key的第一次比较。key比较函数类,这是key的第二次比较。这是一个比较器,需要继承WritableComparator(这就是所谓的二次排序)。然后就是reducer,在不同的第一字段加了个SEPARATOR。例子里面有个FirstGroupingComparator,注意是构造一个key对应的value迭代器的时候,只要first相同就属于同一个组,放在一个value迭代器。。二次排序需要制定这两个。
// group and partition by the first int in the pair
job.setPartitionerClass(FirstPartitioner.class);
job.setGroupingComparatorClass(FirstGroupingComparator.class);
./bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.1.jar secondarysort hdfs://Hadoop-02:9000/testdata hdfs://Hadoop-02:9000/testresult
输入数据:(以tab分隔)
43 12
43 3
43 15
25 4
25 10
25 2
最后的结果是:
------------------------------------------------
25 2
25 4
25 10
------------------------------------------------
43 3
43 12
43 15
这里总结下,排序是MapReduce的很重要的技术,尽管应用程序本身不需要对数据排序,但可以使用MapReduce的排序功能来组织数据。默认情况下,MapReduce根据输入记录的键对数据排序。键的排列顺序是由RawComparator控制的,排序规则如下:
1)若属性mapred.output.key.comparator.class已设置,则使用该类的实例;
2)否则键必须是WritableComparable的子类,并使用针对该键类的已登记的comparator;(例如上例中用到的static块)
static { // register this comparator
WritableComparator.define(IntPair.class, new Comparator());
}
3)如果还没有已登记的comparator,则使用RawComparator将字节流反序列化为一个对象,再由WritableComparable的compareTo()方法进行操作。