上節(jié)課我們一起學(xué)習(xí)了Combiner的理論知識,這節(jié)課我們來一起學(xué)習(xí)一下倒排索引,那么首先我們來了解一下什么是倒排索引,如下圖所示,我們可以看到表中有單詞ID,單詞還有倒排列表,倒排列表存放的是某個單詞在第幾篇文章中出現(xiàn)的次數(shù),比如谷歌這個單詞在第一篇文章中出現(xiàn)了1次,第二篇文章中出現(xiàn)了1次,第三篇文章中出現(xiàn)了2次,第四、第五篇文章各出現(xiàn)1次。那么倒排索引的用處是什么呢?我們以搜索引擎為例,像百度、谷歌這樣大的搜索引擎商家,每天面對的數(shù)據(jù)量是百億、千億甚至更多,如此龐大的數(shù)據(jù)量,如何保證用戶搜索時的快捷高效?這就用到倒排索引了,我們知道用戶搜索東西的時候是通過輸入關(guān)鍵詞來搜索的,百度、谷歌是有自己的關(guān)鍵詞列表的,它們先通過MapReduce建立起一個倒排索引列表,每個關(guān)鍵詞在哪些文章中出現(xiàn)過以及出現(xiàn)過幾次都可以在倒排索引列表快速找到,如此一來當用戶搜索的時候便不用再去所有的文章中挨個查找了,直接便可以定位到相關(guān)的文章,這就極大的提高了查詢效率,提升客戶滿意度。
接下來我們一起來做個倒排索引的簡單的例子,假如我們現(xiàn)在有3個txt文件,里面存放的內(nèi)容如下圖所示,單詞與單詞之間是以一個空格分隔的。
那么根據(jù)上圖的三個文件,我們想要的結(jié)果如下:
hello a.txt->3 b.txt->2 c.txt->2
tom a.txt->1 b.txt->2 c.txt->2
jerry a.txt->1
kitty a.txt->1 b.txt->1
想要得到上面的結(jié)果,一個MapReduce無法完成,我們可以通過兩個MapReduce來完成也可以在Map和Reduce之間加一個Combiner做中間處理來完成,上節(jié)課我們剛學(xué)了Combiner的理論,這節(jié)我們便使用Combiner來處理。我們要想得到最終的結(jié)果,從正面推不好推,我們可以倒著推,我們以"hello"來推,最后一個Reducer輸出的形式是:context.write("hello","a.txt->3 b.txt->2 c.txt->2");,那么傳給Reducer的數(shù)據(jù)形式是:<"hello",{"a.txt->3","b.txt->2","c.txt->2"}>,進而我們知道m(xù)apper輸出的形式是:context.write("hello","a.txt->3");context.write("hello","b.txt->2");context.write("hello","c.txt->2");。那么第一個reducer輸出的形式便是:context.write("hello","a.txt->3");context.write("hello","b.txt->2");context.write("hello","c.txt->2");第一個mapper的輸出就應(yīng)該是context.write("hello->a.txt","1");context.write("hello->a.txt","1");context.write("hello->a.txt","1");。
通過上面的倒推過程,我們基本上知道該怎么做了,現(xiàn)在就通過程序來實現(xiàn)。關(guān)于如何創(chuàng)建一個Maven工程并如何配置pom.xml等內(nèi)容大家可以參考:http://blog.csdn.net/u012453843/article/details/52600313這篇博客來學(xué)習(xí)。由于我們是在前面幾節(jié)課的基礎(chǔ)上學(xué)習(xí)的,我就在一個已有的sortdatacount工程下寫我們的代碼了。如下所示
我們只需要完成一個類ReverseIndex.java即可,現(xiàn)在我把這個類的代碼粘貼出來,如下所示
package myhadoop.mr.index;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
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.input.FileSplit;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class ReverseIndex {
public static void main(String[] args) throws Exception {
Configuration conf=new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(ReverseIndex.class);
//將Mapper類設(shè)置到Job當中
job.setMapperClass(ReverseIndexMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
FileInputFormat.setInputPaths(job, new Path(args[0]));
//將Combiner類設(shè)置到Job當中
job.setCombinerClass(ReverseIndexCombiner.class);
//將Reducer類設(shè)置到Job當中
job.setReducerClass(ReverseIndexReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//等待執(zhí)行結(jié)束
job.waitForCompletion(true);
}
public static class ReverseIndexMapper extends Mapper<LongWritable, Text, Text, Text>{
private Text k=new Text();
private Text v=new Text();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String line=value.toString();
String[] words=line.split(" ");
//我們怎么知道文件在什么位置呢?其實我們可以通過context來獲取到
FileSplit fileSplit=(FileSplit) context.getInputSplit();
//文件所在的位置
String path=fileSplit.getPath().toString();
//我們這個例子比較簡單只有3個文件,還都在同一個目錄下,path目錄是帶著hdfs路徑前綴的
//為了與我們最終的想要的結(jié)果完全一致,我們把路徑進行截取,我們 a.txt,b.txt,c.txt有個
//共同的特點就是長度都是5,因此我們只需要截取路徑最后5個字符就可以了。
String filepath=path.substring(path.length()-5);
for(String word:words){
k.set(word+"->"+filepath);
v.set("1");
context.write(k, v);
}
}
}
public static class ReverseIndexCombiner extends Reducer<Text, Text, Text, Text>{
private Text k=new Text();
private Text v=new Text();
@Override
protected void reduce(Text key, Iterable<Text> values,Context context)
throws IOException, InterruptedException {
String line=key.toString();
String[] wordAndPath=line.split("->");
int sum=0;
for(Text t : values){
sum+=Integer.parseInt(t.toString());
}
k.set(wordAndPath[0]);
v.set(wordAndPath[1]+"->"+sum);
context.write(k, v);
}
}
public static class ReverseIndexReducer extends Reducer<Text, Text, Text, Text>{
private Text v=new Text();
@Override
protected void reduce(Text key, Iterable<Text> values,Context context)
throws IOException, InterruptedException {
StringBuilder builder=new StringBuilder();
for(Text t:values){
builder.append(t+"\t");
}
v.set(builder.toString());
context.write(key, v);
}
}
}
寫完了代碼,現(xiàn)在我們導(dǎo)出這個工程,關(guān)于具體導(dǎo)出的過程大家可以參考:http://blog.csdn.net/u012453843/article/details/52600313這篇博文進行學(xué)習(xí),下面這張圖是導(dǎo)出的最后一步,我們勾選第二個復(fù)選框,JAR file我們寫上/root/reverseindex.jar,然后點擊"Finish"。
導(dǎo)出成功后,我們到root根目錄下看一下是否有我們剛才所取的名為reverseindex.jar,如下圖所示,發(fā)現(xiàn)確實有的。
在執(zhí)行這個程序之前,我們需要把數(shù)據(jù)上傳到hdfs系統(tǒng)上,首先我們先把a.txt,b.txt,c.txt從Windows系統(tǒng)上傳到虛擬機root根目錄下,可以通過Filezilla工具來上傳,如果不知道怎么使用FileZilla,大家可以參考:http://blog.csdn.net/u012453843/article/details/52422736這篇博文進行學(xué)習(xí)。上傳到root根目錄后,我們?nèi)oot根目錄查看一下是否真的已經(jīng)有了,如下圖所示,發(fā)現(xiàn)確實已經(jīng)有a.txt,b.txt,c.txt三個文件了。
接著我們再把這三個文件上傳到hdfs系統(tǒng)上,在上傳之前需要先檢查hdfs和yarn是否都已經(jīng)啟動起來了,如果沒有啟動的話,需要先啟動它們,關(guān)于它們的啟動大家可以參考:http://blog.csdn.net/u012453843/article/details/52433457這篇博客進行學(xué)習(xí)。當我們使用命令jps后如果可以看到NodeManager、ResourceManager、DataNode、SecondaryNameNode、NameNode說明所有的進程都正常啟動起來了。如下圖所示。
現(xiàn)在我們開始將a.txt,b.txt,c.txt文件上傳到hdfs系統(tǒng)根目錄下的index文件夾下,在上傳之前我們需要現(xiàn)在hdfs系統(tǒng)根目錄下新建一個index文件夾,使用命令:hadoop fs -mkdir /index
創(chuàng)建成功后,我們通過命令hadoop fs -ls /來查看一下hdfs系統(tǒng)根目錄下是否有index文件,發(fā)現(xiàn)確實是有的,然后開始上傳文件,我們?nèi)齻€文件一起上傳,使用命令:hadoop fs -put a.txt b.txt c.txt /index來實現(xiàn)上傳,上傳完后我們進入到hdfs系統(tǒng)的index目錄下,看看是否有我們剛才上傳的文件,發(fā)現(xiàn)是有的,如下圖所示。
準備好了一切,我們開始執(zhí)行jar,命令是:hadoop jar reverseindex.jar myhadoop.mr.index.ReverseIndex /index /reverseindex,執(zhí)行成功之后我們看看reverseindex目錄下生成了哪些文件,發(fā)現(xiàn)有兩個文件,其中part-r-00000文件便是生成的結(jié)果文件,我們使用命令:hadoop fs -cat /reverseindex/part-r-00000來查看文件的內(nèi)容,發(fā)現(xiàn)里面確實是我們想要看到的結(jié)果,如下圖所示。