之前弄采样器,以为已经结束了工作,结果现在又遇到了问题,因为我的输入有两个文件,设计要求是先只采样其中的大文件(未来是两个文件分别采样的),只有一个输入文件且采样时,使用采样器的代码是:
- Path input = new Path(args[0].toString());
- input = input.makeQualified(input.getFileSystem(conf));
-
- InputSampler.IntervalSampler<Text, NullWritable> sampler = new InputSampler.IntervalSampler<Text, NullWritable>(0.4, 5);
-
-
- String skewuri_out = args[2] + "/sample_list";
- FileSystem fs = FileSystem.get(URI.create(skewuri_out), conf);
- FSDataOutputStream fs_out = fs.create(new Path(skewuri_out));
-
- final InputFormat inf = conf.getInputFormat();
- Object[] p = sampler.getSample(inf, conf);
但是这样问题就来了,如果我写了两个Mapper类,分别为Map1class,Map2class,现在两个class分别处理两个不同输入路径的数据,目前是指定输入数据的格式是相同的,那么可以用MultipleInputs 来实现:
- MultipleInputs.addInputPath(conf, new Path(args[0]), Definemyself.class,Map1class.class);
- 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中的最重要的 函数。
- class EmployeeRecordReader implements RecordReader<Text, Text> {
- private LineRecordReader in;
- private LongWritable junk = new LongWritable();
- private Text line = new Text();
- private int KEY_LENGTH = 10;
- public boolean next(Text key, Text value) throws IOException {
- if (in.next(junk, line)) {
- String[] lines = line.toString().split(",");
- if (lines.length ==9) {
-
- deptno = lines[7];
-
- if (deptno.length() < KEY_LENGTH) {
- key.set(new Text(deptno));
- value.set(new Text(line+","+"0"));
- } else {
- key.set(new Text(deptno));
- value.set(new Text(line+","+"0"));
- }
- return true;
- }
- else if (lines.length !=9) {
- System.out.println("(for short.txt)The line in next of Employee is "+ line);
- deptno = lines[0];
- if (deptno.length() < KEY_LENGTH ) {
- key.set(new Text(deptno));
- value.set(new Text(line+","+"1"));
- } else{
- key.set(new Text(deptno));
- value.set(new Text(line+","+"1"));
- }
- return true;
- }
- else return false;
-
- } else {
- return false;
- }
- }
- }
以上的更改就是两个表进来,都可通过此类进行输入,无须针对两个表,要写两个继承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)来存储定义分 区的键。
简单的代码(权威指南中的):
- <span style="font-weight: normal;">InputSampler.RandomSampler<IntWritable, NullWritable> sampler = new InputSampler.RandomSampler<IntWritable, NullWritable>(1.0, 20, 3);
- Path input = FileInputFormat.getInputPaths(conf)[0];
- input = input.makeQualified(input.getFileSystem(conf));
- Path partitionFile = new Path(input, "_partitions");
- TotalOrderPartitioner.setPartitionFile(conf, partitionFile);
- InputSampler.writePartitionFile(conf, sampler);
其中 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 的例子。
- SequenceFile.Reader read = null;
- FileSystem fs = FileSystem.get(partitionURI, conf);
- read = new SequenceFile.Reader(fs, partitionFile, conf);
- IntWritable key = (IntWritable) ReflectionUtils.newInstance(read.getKeyClass(), conf);
- NullWritable value = (NullWritable) ReflectionUtils.newInstance(read.getValueClass(), conf);
-
- while(read.next(key, value)){
-
-
-
-
- IOUtils.closeStream(read);
这里 要提醒一下,如果你指定reduce task的个数如果为1,那么即使你采样了,采了很多个,但是writePartitionFile 函数是不会向你指定的顺序文件中写入数据的(想想也是吗?可惜当时脑子坏掉了,就只想着怎么文件是空的,忘记改reduce task的个数了,囧阿)。
这样以来通过读取 partitionFile 所指定的SequenceFile 文件中的数据,就可以获得用于分区的 键值。可以我的需求是要记录每个采样值,并且统计每个采样值出现的次数。找了很久的实现方法,关键是我自己又不想自己重新写一个类来继承 InputSampler。找了很久,根本搜不到(搜到的全是用采样器来帮助TotalOrderParition的,或者Tera排序的)
函数原型如下:
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女真不容易阿。
谨以此篇文章献给我最爱的老公,么么。(你都不用找工作,跑去香港读博,哼)