MapReduce-XML处理-自定义InputFormat及自定义RecordReader

友情提示:更多有关大数据、人工智能方面技术文章请关注博主个人微信公众号:高级大数据架构师

这一篇说明如何自定义InputFormat以及RecordReader这两个组件,通过使用mapreduce处理xml文件格式的文件来说明其用法,这一个例子来自《hadoop硬实战》一书的技术点12讲解的用法,如果有说明得不清楚的可以自行进行查阅
下面就来说说这个实例要达到的目的以下是输入数据:
<configuration>
  <property>
    <name>hadoop.kms.authentication.type</name>
    <value>simple</value>
    <description>
      Authentication type for the KMS. Can be either &quot;simple&quot;
      or &quot;kerberos&quot;.
    </description>
  </property>
  <property>
    <name>hadoop.kms.authentication.kerberos.keytab</name>
    <value>${user.home}/kms.keytab</value>
    <description>
      Path to the keytab with credentials for the configured Kerberos principal.
    </description>
  </property>
  <property>
    <name>hadoop.kms.authentication.kerberos.principal</name>
    <value>HTTP/localhost</value>
    <description>
      The Kerberos principal to use for the HTTP endpoint.
      The principal must start with 'HTTP/' as per the Kerberos HTTP SPNEGO specification.
    </description>
  </property>
  <property>
    <name>hadoop.kms.authentication.kerberos.name.rules</name>
    <value>DEFAULT</value>
    <description>
      Rules used to resolve Kerberos principal names.
    </description>
  </property>
</configuration>
实现的结果:<name>标签中的数据提取出来做为key,把<value>标签中的提取出来做为值进行键值对的输出
hadoop.kms.authentication.kerberos.keytab       ${user.home}/kms.keytab
hadoop.kms.authentication.kerberos.name.rules   DEFAULT
hadoop.kms.authentication.kerberos.principal    HTTP/localhost
hadoop.kms.authentication.type  simple

实现步骤:
1.定制InputFormat实现方法:实现InputFormat接口,或者继承InputFormat的子类,主要实现以下两个方法:
List<InputSplit> getSplits(), 获取由输入文件计算出输入分片(InputSplit),解决数据或文件分割成片问题。
RecordReader<K,V> createRecordReader(),创建RecordReader,从InputSplit中读取数据,解决读取分片中数据问题

 

2.定制RecordReader实现方法:实现RecordReader接口(旧版API)继承RecordReader类(新版API),下面以新版API为例实现以下方法:

  public abstract void initialize(InputSplit split, TaskAttemptContext context ) throws IOException, InterruptedException;
  public abstract boolean nextKeyValue() throws IOException, InterruptedException;
  public abstract KEYIN getCurrentKey() throws IOException, InterruptedException;
  public abstract VALUEIN getCurrentValue() throws IOException, InterruptedException;
  public abstract float getProgress() throws IOException, InterruptedException;
  public abstract void close() throws IOException;
其中nextKeyValue(),getCurrentKey(),getCurrentValue()方法会在Mapper换执行过程中反复调用直到该MAP任务所分到的分片被完全的处理,hadoop1.2.1的源码如下:
public void run(Context context) throws IOException, InterruptedException {
    setup(context);
    try {
    //map通过这里反复调用RecordReader的方法
    while (context.nextKeyValue()) {
    //context.getCurrentKey()在MapContext的方法中调用相关RecordReader的方法
    /**
    * @Override
  * public KEYIN getCurrentKey() throws IOException, InterruptedException {
    * return reader.getCurrentKey();
  * }
    */
        map(context.getCurrentKey(), context.getCurrentValue(), context);
      }
    } finally {
      cleanup(context);
    }
  }

 

最核心的就是处理好迭代多行文本的内容的逻辑,每次迭代通过调用nextKeyValue()方法来判断是否还有可读的文本行,直接设置当前的Key和Value,分别在方法getCurrentKey()和getCurrentValue()中返回对应的值。在实现的代码中会有相应注释说明。

定制InputFormat:

 

[java] view plain copy

  1. import java.io.IOException;  
  2. import org.apache.hadoop.fs.Path;  
  3. import org.apache.hadoop.io.LongWritable;  
  4. import org.apache.hadoop.io.Text;  
  5. import org.apache.hadoop.mapreduce.InputSplit;  
  6. import org.apache.hadoop.mapreduce.JobContext;  
  7. import org.apache.hadoop.mapreduce.RecordReader;  
  8. import org.apache.hadoop.mapreduce.TaskAttemptContext;  
  9. import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;  
  10. import org.slf4j.Logger;  
  11. import org.slf4j.LoggerFactory;  
  12.   
  13. public class XMLInputFormat extends TextInputFormat {  
  14.     private static final Logger log = LoggerFactory.getLogger(XMLInputFormat.class);  
  15.     @Override  
  16.     public RecordReader<LongWritable, Text> createRecordReader(  
  17.             InputSplit inputSplit, TaskAttemptContext context) {  
  18.         try {  
  19.             return new XMLRecordReader(inputSplit, context.getConfiguration());  
  20.         } catch (IOException e) {  
  21.             log.warn("Error while creating XmlRecordReader", e);  
  22.             return null;  
  23.         }  
  24.     }  
  25.     @Override  
  26.     protected boolean isSplitable(JobContext context, Path file) {  
  27.         // TODO Auto-generated method stub  
  28.         return super.isSplitable(context, file);  
  29.     }  
  30. }  

定义RecordReader:(这是xml文件处理的关键)

 

 

[java] view plain copy

  1. import java.io.IOException;  
  2. import org.apache.hadoop.conf.Configuration;  
  3. import org.apache.hadoop.fs.FSDataInputStream;  
  4. import org.apache.hadoop.fs.FileSystem;  
  5. import org.apache.hadoop.fs.Path;  
  6. import org.apache.hadoop.io.DataOutputBuffer;  
  7. import org.apache.hadoop.io.LongWritable;  
  8. import org.apache.hadoop.io.Text;  
  9. import org.apache.hadoop.mapreduce.InputSplit;  
  10. import org.apache.hadoop.mapreduce.RecordReader;  
  11. import org.apache.hadoop.mapreduce.TaskAttemptContext;  
  12. import org.apache.hadoop.mapreduce.lib.input.FileSplit;  
  13.   
  14. public class XMLRecordReader extends RecordReader<LongWritable, Text> {  
  15.     private long start;  
  16.     private long end;  
  17.     private FSDataInputStream fsin;  
  18.     private DataOutputBuffer buffer = new DataOutputBuffer();  
  19.     private byte[] startTag;  
  20.     private byte[] endTag;  
  21.     private LongWritable currentKey;  
  22.     private Text currentValue;  
  23.     public static final String START_TAG_KEY = "xmlinput.start";  
  24.     public static final String END_TAG_KEY = "xmlinput.end";  
  25.     public XMLRecordReader() {  
  26.     }  
  27.     /** 
  28.      * 初始化读取资源以及相关的参数也可以放到initialize()方法中去执行 
  29.      * @param inputSplit 
  30.      * @param context 
  31.      * @throws IOException 
  32.      */  
  33.     public XMLRecordReader(InputSplit inputSplit, Configuration context) throws IOException {  
  34.         /** 
  35.          * 获取开传入的开始和结束标签 
  36.          */  
  37.         startTag = context.get(START_TAG_KEY).getBytes("UTF-8");  
  38.         endTag = context.get(END_TAG_KEY).getBytes("UTF-8");  
  39.         FileSplit fileSplit = (FileSplit) inputSplit;  
  40.         /** 
  41.          * 获取分片的开始位置和结束的位置 
  42.          */  
  43.         start = fileSplit.getStart();  
  44.         end = start + fileSplit.getLength();  
  45.         Path file = fileSplit.getPath();  
  46.         FileSystem fs = file.getFileSystem(context);  
  47.         /** 
  48.          * 根据分片打开一个HDFS的文件输入流 
  49.          */  
  50.         fsin = fs.open(fileSplit.getPath());  
  51.         /** 
  52.          * 定位到分片开始的位置 
  53.          */  
  54.         fsin.seek(start);  
  55.     }  
  56.     @Override  
  57.     public void close() throws IOException {  
  58.         fsin.close();  
  59.     }  
  60.     @Override  
  61.     public LongWritable getCurrentKey() throws IOException, InterruptedException {  
  62.         return currentKey;  
  63.     }  
  64.     @Override  
  65.     public Text getCurrentValue() throws IOException, InterruptedException {  
  66.         return currentValue;  
  67.     }  
  68.     @Override  
  69.     public float getProgress() throws IOException, InterruptedException {  
  70.         return fsin.getPos() - start / (float) end - start;  
  71.     }  
  72.     @Override  
  73.     public void initialize(InputSplit inputSplit, TaskAttemptContext context)  
  74.             throws IOException, InterruptedException {  
  75.         /*startTag = context.getConfiguration().get(START_TAG_KEY).getBytes("UTF-8"); 
  76.         endTag = context.getConfiguration().get(END_TAG_KEY).getBytes("UTF-8"); 
  77.         FileSplit fileSplit = (FileSplit) inputSplit; 
  78.         start = fileSplit.getStart(); 
  79.         end = start + fileSplit.getLength(); 
  80.         Path file = fileSplit.getPath(); 
  81.         FileSystem fs = file.getFileSystem(context.getConfiguration()); 
  82.         fsin = fs.open(fileSplit.getPath()); 
  83.         fsin.seek(start);*/  
  84.     }  
  85.     @Override  
  86.     public boolean nextKeyValue() throws IOException, InterruptedException {  
  87.         currentKey = new LongWritable();  
  88.         currentValue = new Text();  
  89.         return next(currentKey, currentValue);  
  90.     }  
  91.     private boolean next(LongWritable key, Text value) throws IOException {  
  92.         /** 
  93.          *  通过readUntilMatch方法查找xml段开始的标签,直到找到了,才开始 
  94.          *  写xml片段到buffer中去,如readUntilMatch的第二个参数为false则不查找的过 
  95.          *  程中写入数据到buffer,如果为true的话就边查找边写入 
  96.          */  
  97.         if( fsin.getPos() < end && readUntilMatch(startTag, false)) {  
  98.             //进入代码段则说明找到了开始标签,现在fsin的指针指在找到的开始标签的  
  99.             //最后一位上,所以向buffer中写入开始标签  
  100.             buffer.write(startTag);  
  101.             try {  
  102.                 /** 
  103.                  * 在fsin中去查找结束标签边查找边记录直到找到结束标签为止 
  104.                  */  
  105.                 if(readUntilMatch(endTag, true)) {  
  106.                     /** 
  107.                      * 找到标签后把结束标签的指针位置的偏移量赋值给key 
  108.                      * 把buffer中记录的整个xml完整片断赋值给value 
  109.                      */  
  110.                     key.set(fsin.getPos());  
  111.                     value.set(buffer.getData(), 0, buffer.getLength());  
  112.                     return true;  
  113.                 }  
  114.             } finally {  
  115.                 buffer.reset();  
  116.             }  
  117.         }  
  118.         return false;  
  119.     }  
  120.     /** 
  121.      * 读取xml文件匹配标签的方法 
  122.      * @param startTag 
  123.      * @param isWrite 
  124.      * @return 
  125.      * @throws IOException 
  126.      */  
  127.     private boolean readUntilMatch(byte[] startTag, boolean isWrite) throws IOException {  
  128.         int i = 0;  
  129.         while(true) {  
  130.             /** 
  131.              * 从输入文件只读取一个Byte的数据 
  132.              */  
  133.             int b = fsin.read();  
  134.             if( b == -1) {  
  135.                 return false;  
  136.             }  
  137.             /** 
  138.              *  如果在查找开始标签则不记录查找过程, 
  139.              *  在查找结束标签时才记录查找过程。 
  140.              */  
  141.             if(isWrite) {  
  142.                 buffer.write(b);  
  143.             }  
  144.             /** 
  145.              * 判断时否找到指定的标签来判断函数结束的时间点 
  146.              */  
  147.             if(b == startTag[i]) {  
  148.                 i ++;  
  149.                 if( i >= startTag.length) {  
  150.                     return true;  
  151.                 }  
  152.             } else {  
  153.                 i = 0;  
  154.             }  
  155.             // see if we've passed the stop point:  
  156.             if (!isWrite && i == 0 && fsin.getPos() >= end) {  
  157.                 return false;  
  158.             }  
  159.         }  
  160.     }  
  161. }  

map阶段:

 

 

[java] view plain copy

  1. import static javax.xml.stream.XMLStreamConstants.CHARACTERS;  
  2. import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;  
  3. import java.io.ByteArrayInputStream;  
  4. import java.io.IOException;  
  5. import javax.xml.stream.FactoryConfigurationError;  
  6. import javax.xml.stream.XMLInputFactory;  
  7. import javax.xml.stream.XMLStreamException;  
  8. import javax.xml.stream.XMLStreamReader;  
  9. import org.apache.hadoop.io.LongWritable;  
  10. import org.apache.hadoop.io.Text;  
  11. import org.apache.hadoop.mapreduce.Mapper;  
  12.   
  13. public class XMLMapper extends Mapper<LongWritable, Text, Text, Text>{  
  14.     /** 
  15.      *  从XMLRecordReader可以知道传入的value为指定的包含开始标签和结束标签 
  16.      *  的一个片断 
  17.      */  
  18.     @Override  
  19.     protected void map(LongWritable key, Text value, Context context)  
  20.             throws IOException, InterruptedException {  
  21.         String content = value.toString();  
  22.         System.out.println("--content--");  
  23.         try {  
  24.             //把value的值转化为一个XML的输入流,以便后继的处理  
  25.             XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(  
  26.                     new ByteArrayInputStream(content.getBytes()));  
  27.             String propertyName = "";  
  28.             String propertyValue = "";  
  29.             String currentElement = "";  
  30.             /** 
  31.              * 定义处理业务可以通过下面的main函数来看这段程序的实现的功能 
  32.              */  
  33.             while (reader.hasNext()) {  
  34.                 int code = reader.next();  
  35.                 switch (code) {  
  36.                 case START_ELEMENT:  
  37.                     currentElement = reader.getLocalName();  
  38.                     break;  
  39.                 case CHARACTERS:  
  40.                     if (currentElement.equalsIgnoreCase("name")) {  
  41.                         propertyName += reader.getText();  
  42.                     } else if (currentElement.equalsIgnoreCase("value")) {  
  43.                         propertyValue += reader.getText();  
  44.                     }  
  45.                     break;  
  46.                 }  
  47.             }  
  48.             reader.close();  
  49.             context.write(new Text(propertyName.trim()), new Text(propertyValue.trim()));  
  50.         } catch (XMLStreamException e) {  
  51.             e.printStackTrace();  
  52.         } catch (FactoryConfigurationError e) {  
  53.             e.printStackTrace();  
  54.         }  
  55.     }  
  56.     public static void main(String[] args) {  
  57.         String content = "<property><name>seven</name><value>24</value></property>";  
  58.         System.out.println("--content--");  
  59.         try {  
  60.             XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(  
  61.                     new ByteArrayInputStream(content.getBytes()));  
  62.             String propertyName = "";  
  63.             String propertyValue = "";  
  64.             String currentElement = "";  
  65.             while (reader.hasNext()) {  
  66.                 int code = reader.next();  
  67.                 switch (code) {  
  68.                 case START_ELEMENT:  
  69.                     currentElement = reader.getLocalName();  
  70.                     break;  
  71.                 case CHARACTERS:  
  72.                     if (currentElement.equalsIgnoreCase("name")) {  
  73.                         propertyName += reader.getText();  
  74.                     } else if (currentElement.equalsIgnoreCase("value")) {  
  75.                         propertyValue += reader.getText();  
  76.                     }  
  77.                     break;  
  78.                 }  
  79.             }  
  80.             reader.close();  
  81.             System.out.println(propertyName + " " + propertyValue);  
  82.         } catch (XMLStreamException e) {  
  83.             e.printStackTrace();  
  84.         } catch (FactoryConfigurationError e) {  
  85.             e.printStackTrace();  
  86.         }  
  87.     }  
  88. }  

reduce阶段:

 

 

[java] view plain copy

  1. import java.io.IOException;  
  2. import org.apache.hadoop.io.Text;  
  3. import org.apache.hadoop.mapreduce.Reducer;  
  4.   
  5. public class XMLReducer extends Reducer<Text, Text, Text, Text>{  
  6.     private Text val_ = new Text();  
  7.     @Override  
  8.     protected void reduce(Text key, Iterable<Text> value, Context context)  
  9.             throws IOException, InterruptedException {  
  10.         for(Text val: value) {  
  11.             val_.set(val.toString());  
  12.             context.write(key, val_);  
  13.         }  
  14.     }  
  15. }  

启动函数:

 

 

[java] view plain copy

  1. import org.apache.hadoop.conf.Configuration;  
  2. import org.apache.hadoop.fs.Path;  
  3. import org.apache.hadoop.io.Text;  
  4. import org.apache.hadoop.mapreduce.Job;  
  5. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  6. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  7.   
  8. public class JobMain {  
  9.     public static void main(String[] args) throws Exception{  
  10.         Configuration configuration = new Configuration();  
  11.         configuration.set("key.value.separator.in.input.line"" ");  
  12.         configuration.set("xmlinput.start""<property>");  
  13.         configuration.set("xmlinput.end""</property>");  
  14.         Job job = new Job(configuration, "xmlread-job");  
  15.         job.setJarByClass(JobMain.class);  
  16.         job.setMapperClass(XMLMapper.class);  
  17.         job.setMapOutputKeyClass(Text.class);  
  18.         job.setMapOutputValueClass(Text.class);  
  19.         job.setInputFormatClass(XMLInputFormat.class);  
  20.         job.setNumReduceTasks(1);  
  21.         job.setReducerClass(XMLReducer.class);  
  22.         //job.setOutputFormatClass(XMLOutputFormat.class);  
  23.         FileInputFormat.addInputPath(job, new Path(args[0]));  
  24.         Path output = new Path(args[1]);  
  25.         FileOutputFormat.setOutputPath(job, output);  
  26.         output.getFileSystem(configuration).delete(output, true);  
  27.         System.exit(job.waitForCompletion(true) ? 01);  
  28.     }  
  29. }  

运行结果:

 


结论:

这里通过mapreduce对xml输入文件的处理说了InputFormat以及RecordReader的定制,下一篇将基于这个实例说明OutputFormat以及RecordWriter的定制,实例将把最后结果 输出为xml格式的文件,可参见《MapReduce-XML处理-定制OutputFormat及定制RecordWriter》。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值