今天在偶然看到一个博文,里面讲述如何使用mapreduce进行倒排索引处理。那就拿这个任务当成本篇实战任务吧。
一、任务描述

hdfs 上有三个文件,内容下上面左面框中所示。右框中为处理完成后的结果文件。
倒排索引(Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”
这个任务与传统的倒排索引任务不同的地方是加上了每个文件中的频数。
二、实现思路
-
首先关注结果中有文件名称,这个我们有两种方式处理:1、自定义InputFormat,在其中的自定义RecordReader中,直接通过InputSplit得到Path,继而得到FileName;2、在Mapper中,通过上下文可以取到Split,也可以得到fileName。这个任务中我们使用第二种方式,得到filename.
-
在mapper中,得到filename 及 word,封装到一个自定义keu中。value 使用IntWritable。在map 中直接输出值为1的IntWritable对象。
-
对进入reduce函数中的key进行分组控制,要求按word相同的进入同一次reduce调用。所以需要自定义GroupingComparator。
三、实现代码
自定义Key, WordKey代码。
注意这里面有个故意设置的坑。
-
package indexinverted;
-
-
import java.io.DataInput;
-
import java.io.DataOutput;
-
import java.io.IOException;
-
-
import org.apache.hadoop.io.WritableComparable;
-
-
-
public class WordKey implements WritableComparable <WordKey> {
-
-
private String fileName;
-
private String word;
-
-
@Override
-
public void write(DataOutput out) throws IOException {
-
out.writeUTF(fileName);
-
out.writeUTF(word);
-
}
-
-
@Override
-
public void readFields(DataInput in) throws IOException {
-
this.fileName = in.readUTF();
-
this.word = in.readUTF();
-
}
-
-
@Override
-
public int compareTo(WordKey key) {
-
int r = fileName.compareTo(key.fileName);
-
if(r==0)
-
r = word.compareTo(key.word);
-
return r;
-
}
-
-
public String getFileName() {
-
return fileName;
-
}
-
-
public void setFileName(String fileName) {
-
this.fileName = fileName;
-
}
-
-
public String getWord() {
-
return word;
-
}
-
-
public void setWord(String word) {
-
this.word = word;
-
}
-
}
Mapper、 Reducer、
IndexInvertedGroupingComparator (我喜欢把一些小的类当成内部类,放到Job类中,这样代码比较简单)
Reduce函数中处理输出结果有点繁琐,可以不用太关注。
-
package indexinverted;
-
import java.io.IOException;
-
import java.util.HashMap;
-
import java.util.LinkedHashMap;
-
-
import org.apache.hadoop.conf.Configuration;
-
import org.apache.hadoop.conf.Configured;
-
import org.apache.hadoop.fs.FileSystem;
-
import org.apache.hadoop.fs.Path;
-
import org.apache.hadoop.io.IntWritable;
-
import org.apache.hadoop.io.LongWritable;
-
import org.apache.hadoop.io.Text;
-
import org.apache.hadoop.io.WritableComparable;
-
import org.apache.hadoop.io.WritableComparator;
-
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.input.FileSplit;
-
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
-
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
-
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
-
import org.apache.hadoop.util.Tool;
-
import org.apache.hadoop.util.ToolRunner;
-
import org.apache.log4j.Logger;
-
-
-
public class MyIndexInvertedJob extends Configured implements Tool{
-
-
public static class IndexInvertedMapper extends Mapper<LongWritable,Text,WordKey,IntWritable>{
-
private WordKey newKey = new WordKey();
-
private IntWritable ONE = new IntWritable(1);
-
private String fileName ;
-
-
@Override
-
protected void map(LongWritable key, Text value, Context context)
-
throws IOException, InterruptedException {
-
newKey.setFileName(fileName);
-
String words [] = value.toString().split(" ");
-
for(String w:words){
-
newKey.setWord(w);
-
context.write(newKey, ONE);
-
}
-
}
-
@Override
-
protected void setup(Context context) throws IOException,
-
InterruptedException {
-
FileSplit inputSplit = (FileSplit) context.getInputSplit();
-
fileName = inputSplit.getPath().getName();
-
}
-
-
}
-
public static class IndexInvertedReducer extends Reducer<WordKey,IntWritable,Text,Text>{
-
private Text outputKey = new Text();
-
-
@Override
-
protected void reduce(WordKey key, Iterable<IntWritable> values,Context context)
-
throws IOException, InterruptedException {
-
outputKey.set(key.getWord());
-
LinkedHashMap <String,Integer> map = new LinkedHashMap<String,Integer>();
-
for(IntWritable v :values){
-
if(map.containsKey(key.getFileName())){
-
map.put(key.getFileName(), map.get(key.getFileName())+ v.get());
-
}
-
else{
-
map.put(key.getFileName(), v.get());
-
}
-
}
-
StringBuilder sb = new StringBuilder();
-
sb.append("{");
-
for(String k: map.keySet()){
-
sb.append("(").append(k).append(",").append(map.get(k)).append(")").append(",");
-
}
-
sb.deleteCharAt(sb.length()-1).append("}");
-
context.write(outputKey, new Text(sb.toString()));
-
}
-
-
}
-
public static class IndexInvertedGroupingComparator extends WritableComparator{
-
Logger log = Logger.getLogger(getClass());
-
public IndexInvertedGroupingComparator(){
-
super(WordKey.class,true);
-
}
-
-
@Override
-
public int compare(WritableComparable a, WritableComparable b) {
-
WordKey key1 = (WordKey) a;
-
WordKey key2 = (WordKey) b;
-
log.info("==============key1.getWord().compareTo(key2.getWord()):"+key1.getWord().compareTo(key2.getWord()));
-
return key1.getWord().compareTo(key2.getWord());
-
}
-
-
}
-
@Override
-
public int run(String[] args) throws Exception {
-
Job job = Job.getInstance(getConf(), "IndexInvertedJob");
-
job.setJarByClass(getClass());
-
Configuration conf = job.getConfiguration();
-
-
Path in = new Path("myinvertedindex/");
-
Path out = new Path("myinvertedindex/output");
-
FileSystem.get(conf).delete(out,true);
-
FileInputFormat.setInputPaths(job, in);
-
FileOutputFormat.setOutputPath(job, out);
-
-
job.setInputFormatClass(TextInputFormat.class);
-
job.setOutputFormatClass(TextOutputFormat.class);
-
-
job.setMapperClass(IndexInvertedMapper.class);
-
job.setMapOutputKeyClass(WordKey.class);
-
job.setMapOutputValueClass(IntWritable.class);
-
-
job.setReducerClass(IndexInvertedReducer.class);
-
job.setOutputKeyClass(Text.class);
-
job.setOutputValueClass(Text.class);
-
-
job.setGroupingComparatorClass(IndexInvertedGroupingComparator.class);
-
-
return job.waitForCompletion(true)?0:1;
-
}
-
-
public static void main(String [] args){
-
int r = 0 ;
-
try{
-
r = ToolRunner.run(new Configuration(), new MyIndexInvertedJob(), args);
-
}catch(Exception e){
-
e.printStackTrace();
-
}
-
System.exit(r);
-
}
-
-
}
四、查看结果
hadoop dfs -ca
t myinvertedindex/output/part-r-00000
-
MapReduce {(1.txt,1)}
-
is {(1.txt,1)}
-
simple {(1.txt,1)}
-
MapReduce {(2.txt,1)}
-
is {(2.txt,2)}
-
powerful {(2.txt,1)}
-
simple {(2.txt,1)}
-
Hello {(3.txt,1)}
-
MapReduce {(3.txt,2)}
-
bye {(3.txt,1)}
查看结果发现问题:单词并没有合并到一起,这会是什么原因?
五、深坑回填
查看结果发现问题:单词并没有合并到一起,这是什么原因?
GroupingComparator 是在什么样的基础上起作用的?是配置了按word相同输入到同一次reduce调用就一定会相同的word都进入同一个reduce调用吗?
NO! Groupingcomparator中只是比较了相临的两个key是否相等。所有要结果正确,就要保存key的排序与GroupingComparator的排序是相协调。
问题出在了WordKey的排序是先按文件再按单词进行排,这样相临的key并不是单词相同的,而是文件相同的。
所以要改下WordKey的comparTo方法 。修复后代码如下:
-
package indexinverted;
-
-
import java.io.DataInput;
-
import java.io.DataOutput;
-
import java.io.IOException;
-
-
import org.apache.hadoop.io.WritableComparable;
-
-
-
public class WordKey implements WritableComparable <WordKey> {
-
-
private String fileName;
-
private String word;
-
-
@Override
-
public void write(DataOutput out) throws IOException {
-
out.writeUTF(fileName);
-
out.writeUTF(word);
-
}
-
-
@Override
-
public void readFields(DataInput in) throws IOException {
-
this.fileName = in.readUTF();
-
this.word = in.readUTF();
-
}
-
-
@Override
-
public int compareTo(WordKey key) {
-
int r = word.compareTo(key.word);
-
if(r==0)
-
r = fileName.compareTo(key.fileName);
-
return r;
-
}
-
-
public String getFileName() {
-
return fileName;
-
}
-
-
public void setFileName(String fileName) {
-
this.fileName = fileName;
-
}
-
-
public String getWord() {
-
return word;
-
}
-
-
public void setWord(String word) {
-
this.word = word;
-
}
-
}
运行结果
-
Hello {(3.txt,1)}
-
MapReduce {(1.txt,1),(2.txt,1),(3.txt,2)}
-
bye {(3.txt,1)}
-
is {(1.txt,1),(2.txt,2)}
-
powerful {(2.txt,1)}
-
simple {(1.txt,1),(2.txt,1)}
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/30066956/viewspace-2120238/
,如需转载,请注明出处,否则将追究法律责任。