hadoop2.2原理:采样器

多输入路径-只采一个文件-(MultipleInputs+getsample(conf.getInputFormat)

之前弄采样器,以为已经结束了工作,结果现在又遇到了问题,因为我的输入有两个文件,设计要求是先只采样其中的大文件(未来是两个文件分别采样的),只有一个输入文件且采样时,使用采样器的代码是:

 

[java] view plain copy
 
  1. Path input = new Path(args[0].toString());  
  2. input = input.makeQualified(input.getFileSystem(conf));  
  3.   
  4. InputSampler.IntervalSampler<Text, NullWritable> sampler = new InputSampler.IntervalSampler<Text, NullWritable>(0.45);  
[java] view plain copy
 
  1. // 这句话的意思是两个分区,  
[java] view plain copy
 
  1. // K[] getSample(InputFormat<K,V> inf, JobConf job)  函数原型  
  2.   
  3. String skewuri_out = args[2] + "/sample_list"// 存放采样的结果,不是分区的结果  
  4. FileSystem fs = FileSystem.get(URI.create(skewuri_out), conf);  
  5. FSDataOutputStream fs_out = fs.create(new Path(skewuri_out));  
  6.   
  7. final InputFormat inf = conf.getInputFormat();//这个是获得Jobconf的InputFormat  
  8. Object[] p = sampler.getSample(inf, conf);// 输出采样的结果,必须前面是Object类型,换成I那头Writable就不管用了,不知道为什么  




 

但是这样问题就来了,如果我写了两个Mapper类,分别为Map1class,Map2class,现在两个class分别处理两个不同输入路径的数据,目前是指定输入数据的格式是相同的,那么可以用MultipleInputs 来实现:

 

[java] view plain copy
 
  1. MultipleInputs.addInputPath(conf, new Path(args[0]), Definemyself.class,Map1class.class);  
  2. MultipleInputs.addInputPath(conf, new Path(args[1]), Definemyself.class,Map2class.class);  


 

//Definemyself.class 是我自定义的继承了FileInputFormat ,并且实现了WritableComparable接口

//继承FileInputFormat 是采样的需要,实现WritableComparable接口,是因为我在join的时候想整体数据进行序列化,我自己也解释不明白这个序列化,可以理解成C里面的结构体吧,就是作为一个整体,可以toString()输出。

原型是:public class Definemyself extends FileInputFormat<Text,Text> implements WritableComparable{...}

这个问题从昨晚就困扰我,上周做梦采样,这种做梦还是采样。中午和老公出去吃的,因为要好好探讨一下这个问题,我的理论就是既然系统提供 MultipleInputs,同时Jobconf有能调用getInputFormat(),就肯定有办法二者同时使用,不让就矛盾了,傻子才会建立这 样的系统呢。

终于,吃完饭回来,我又尝试了一下想法,终于让我给解决了,即如何有两个输入路径的情况下,只对一个输入路径中的文件进行采样,啊哈,答案很简单!见下:

首先修改 Definemyself.class,使其在输出的时候能够分辨是哪个表的,这里我使用最简单的方法,两个表的长度不一样,我通过,长 的我称为long表,短的我成为short表,主要就是修改这个class里面的next函数。以下是Definemyself.class中的最重要的 函数。

 

[java] view plain copy
 
  1. class EmployeeRecordReader implements RecordReader<Text, Text> {  
  2.                 private LineRecordReader in;  
  3. private LongWritable junk = new LongWritable();  
  4. private Text line = new Text();  
  5. private int KEY_LENGTH = 10;  
  6. public boolean next(Text key, Text value) throws IOException {  
  7. if (in.next(junk, line)) {  
  8. String[] lines = line.toString().split(",");  
  9. if (lines.length ==9) { // 这个是大表,也就是要采样的表  
  10. // System.out.println("The line in next of Employee is "+ line);  
  11.                                         deptno = lines[7];  
  12. // 很多写全排序的,不论是对整数,还是对字符串,都采用的下面这种写法,自己查去,不过对于表的一行有很多列,只取其中的一列作为排序的key时 value.set要做更改。就是输出正行,别的博客中都是些的value = new Text(), 太局限了,只适合字符串全局排序时使用。其实下 面的if... else 条件可以省略,留下只是提醒自己,如果从表中抽取的key值占的字节太长时,要做一些处理。  
  13. if (deptno.length() < KEY_LENGTH) {  
  14.     key.set(new Text(deptno));  
  15.     value.set(new Text(line+","+"0"));// 题阿  
  16. else {  
  17.     key.set(new Text(deptno));  
  18.     value.set(new Text(line+","+"0"));  
  19. }  
  20. return true;  
  21. }   
  22. else if (lines.length !=9) {// 断表的处理  
  23. System.out.println("(for short.txt)The line in next of Employee is "+ line);  
  24. deptno = lines[0];// 每列较长的文件中,貌似第5列是depto吧,先这么指定吧  
  25. if (deptno.length() < KEY_LENGTH /* &&(!lines[8].equals("0")) */) {  
  26.     key.set(new Text(deptno));  
  27.     value.set(new Text(line+","+"1"));  
  28. else{  
  29.     key.set(new Text(deptno));  
  30.     value.set(new Text(line+","+"1"));// 1为  
  31. }  
  32. return true;  
  33. }  
  34. else return false;  
  35.   
  36. else {  
  37. return false;  
  38. }  
  39. }  
  40. }  



 

以上的更改就是两个表进来,都可通过此类进行输入,无须针对两个表,要写两个继承FileInputFormat并实现 WritableComparable接口的类。下面才是如何让才采样器只采一个文件的,啊哈!答案说出来笑死人了,那就是利用 MultipleInputs先指定要采样的那个输入路径,然后调用采样器,采样结束后于采样相关的流、文件什么的进行关闭,最后再用 MultipleInputs指定第二个输入路径。这样路径一的文件(可以包含多个文本,你懂的)先采样,然后路径一和路径二的文件都进入map 了,map再根据一些额外的信息判断来自那个路径的数据。

        

MultipleInputs.addInputPath(conf, new Path(args[0]), Definemyself.class,Mapclass.class);//第一个输入路径

        /*********下面采样**********更多采样的细节见我领一篇博客,不一样的视角那篇***********/



        Path input = new Path(args[0].toString());

        input = input.makeQualified(input.getFileSystem(conf));



        InputSampler.RandomSampler<Text, NullWritable> sampler = new InputSampler.RandomSampler<Text, NullWritable>(0.4,20, 5);



        /...........此处省略细节................/



        IOUtils.closeStream(fs_out);// 关闭流,有关采样的结束了。

         /...............此处添加一些其他的需要的工作,例如分布式缓存啦,Hashtable的处理阿............../



         MultipleInputs.addInputPath(conf, new Path(args[3]), Definemyself.class, Mapclass.class); //最后指定输入的第二条路径

 

         JobClient.runJob(conf);

 

今天上午最郁闷的就是,碰到很多问题,根本就搜不到,我最讨厌转别人的博客了,如果你觉得别人的博客有用,你自己保存到草稿箱里就好了阿,一搜索就找到一堆相同的东西,无聊,浪费别人时间,让自己积分高了又怎样阿,我保证以后自己的博客,发表出来的一定要是原创。

最近几天遇到的问题刚开始的时候都会让我头大,因为搜就搜不到答案,Google外国的网页又打不开,你懂的。求人不如求自己,这不最后都解决了 吗?或许对于高手来讲这都不是问题,不要嘲笑我阿,《编程珠玑》上都说了,程序出错的地方不是难的地方,难的地方因为我们会事先考虑好,并设计好,难的地 方是一些容易忽略的地方。渐渐我爱上Hadoop了。

 


 

由于最近在研究Hadoop中采样的问题,搞的头很大,今天慢慢有些头绪了。先记录点采样器的问题吧。

Hadoop已经内置的若干个采 样器, InputSampler 类实现了Sampler接口,该接口的唯一成员方法是getsampler,返回一系列样本键。这个接口通常不直接由客户端调用,二十由 InputSampler类的静态方法writePartitionFile()调用,目的是创建一个顺序文件(SequenceFile)来存储定义分 区的键。
简单的代码(权威指南中的):

 

[java] view plain copy
 
  1. <span style="font-weight: normal;">InputSampler.RandomSampler<IntWritable, NullWritable> sampler = new InputSampler.RandomSampler<IntWritable, NullWritable>(1.0203);  
  2. Path input = FileInputFormat.getInputPaths(conf)[0];  
  3. input = input.makeQualified(input.getFileSystem(conf));  
  4. Path partitionFile = new Path(input, "_partitions");  
  5. TotalOrderPartitioner.setPartitionFile(conf, partitionFile);  
  6. InputSampler.writePartitionFile(conf, sampler);// 目的是创建一个顺序文件来存储定义的分区的键, 这个顺序文件就是路径partitionFile 所指示的文件。</span>  

 

 

其中 writePartitionFile(JobConf job, InputSampler.Sampler<K,V> sampler)这个函数是将采样的结果排序,然后按照分区的个数n,将排序后的结果平均分为n分,取n-1个分割点,这个分割点具体取的时候,运用了一些4舍5入的方法,最简答的理解就是取后n-1个组中每组的第一个采样值就OK了。


上面提到了 SequenceFile. 那这里就简单插一下SequenceFile文件的读问题吧。 目的是用于记录二进制类型的key/value对的,SequenceFile 同样也可作为小文件的容器。提供了Writer,Reader 和 SequenceFile.Sorter 三个类用于完成写,读,和排序。
以下是读一个SequenceFIle 的例子。

[java] view plain copy
 
  1. SequenceFile.Reader read = null;  
  2.  FileSystem fs = FileSystem.get(partitionURI, conf);  
  3. read = new SequenceFile.Reader(fs, partitionFile, conf);  
  4. IntWritable key = (IntWritable) ReflectionUtils.newInstance(read.getKeyClass(), conf); //顺序文件中的key类型  
  5. NullWritable value = (NullWritable) ReflectionUtils.newInstance(read.getValueClass(), conf);//value类型   
  6.   
  7. while(read.next(key, value)){  
  8. // do something you want  
  9.   
  10.        // System.out.println(" The key is "+ key.toString());}  
  11.   
  12. IOUtils.closeStream(read); //最后要关闭read  

 

这里 要提醒一下,如果你指定reduce task的个数如果为1,那么即使你采样了,采了很多个,但是writePartitionFile 函数是不会向你指定的顺序文件中写入数据的(想想也是吗?可惜当时脑子坏掉了,就只想着怎么文件是空的,忘记改reduce task的个数了,囧阿)。

这样以来通过读取 partitionFile 所指定的SequenceFile 文件中的数据,就可以获得用于分区的 键值。可以我的需求是要记录每个采样值,并且统计每个采样值出现的次数。找了很久的实现方法,关键是我自己又不想自己重新写一个类来继承 InputSampler。找了很久,根本搜不到(搜到的全是用采样器来帮助TotalOrderParition的,或者Tera排序的)


函数原型如下:

K[]

 

getSample(InputFormat<K,V> inf, JobConf job) 
          Randomize the split order, then take the specified number of keys from each split sampled, where each key is selected with the specified probability and possibly replaced by a subsequently selected key when the quota of keys from that split is satisfied.

 


顾名思义,k[]是用来获得采样的结果序列的。可是实现时又遇到问题。按照我自己设定的输入输出格式,写了如下代码:

IntWrtiable[] k;

InputFormat<IntWritable, NullWritable> inf;

k = sampler.getSample(inf,conf);

之后就是一堆错误,什么key的类型不匹配阿,还有这样定义inf就是错误的,可怜我java不熟悉,有时候很简单的东西我还是弄错,没办法,慢慢进步吧。

后来发现不能定义inf,而是将inf参数写成 conf.getInputFormat() 这样参数部分就没有什么类型不匹配的问题了,又遇到了新的问题,即使我输入是key是IntWritable类型的,可以写成

IntWritable[] k = sampler.getsample(conf.getInputFormat(),conf)又报错,是呢么key是object 不能强制转化成IntWritable。OMG,发现API上将的根本就不详细阿。而且gersample的使用,我搜索都没搜到,我恨不得去 StackOverFlow上提问去了。

把老公叫过来调程序,我俩折腾了半天,发现要这样写:

Object[] p = sampler.getSample(conf.getInputFormat(), conf);

这样就OK了,如果要把这 些采样结果写进单独的文件中,就使用 FileSystem 和FSDataOutputStream就可以了,要统计结果,定义一个私有静态 hash表就可以了。采样器是我本解段工作的最后一个模块了,下一步就是整合之前的工作了,6月底的时候希望程序和测试实验都结束。

最近很累,又要做项目,又要准备找工作,做个IT女真不容易阿。

谨以此篇文章献给我最爱的老公,么么。(你都不用找工作,跑去香港读博,哼)

 

你可能感兴趣的:(hadoop2)