调研系列第二篇:HCatalog简介
1. 一般的hdfs读写
传统的对于hdfs的读写都是直接设置inputPath 和 outPath ,而且对于数据都是以文件的形式访问的,不涉及到结构化/半结构化的东东,及时如hive存储在hdfs的的结构化数据,外部系统访问也只能自己去了解具体的结构是如何存储的,然后自己读文件再访问,传统访问hdfs的方式如下:
使用InputFormat、Split、RecordReader用于读,具体方式如下:
List<InputSplit> InputFormat.getSplits() ;
RecordReader InputFormat. createRecordReader(InputSplit split) ;
RecordReader. nextKeyValue();
RecordReader. getCurrentValue();
这样的流程来访问 。
使用OutputFormat、RecordWriter来写入hdfs数据,具体如下:
OutputFormat. getRecordWriter();
RecordWriter.write(K key, V value)
对于一般的table结构数据,都是将Key值置成NullWritable,作为一个无用的值来存储。
2. Hcatalog与hdfs的结合:定义了文件的schema
HCatalog将每份结构化的hdfs数据定义schema和访问信息(db、table、partition),然后读和写的时候使用db、table、partition(对于无partition这个可以为空)这三部分信息来访问相应的表数据,屏蔽掉表底层InputFormat、OutFormat以及path信息,读&&写时候只许关系以下几个访问类即可:
HCatInputFormat<WritableComparable, HCatRecord>
HCatRecordReader<WritableComparable, HCatRecord>
HCatOutputFormat<WritableComparable<?>, HCatRecord>
FileRecordWriterContainer<WritableComparable<?>, HCatRecord>
几个类就行了,读写的接口Value均是HCatRecord对象,Key值是WritableComparable即可,不过一般key值都是NullWritable,并无实际用途 。
3. 对象HCatRecord
内部存储这Object[]数组,分别存储一行数据中每列的值,为hive基本类型的Java对象(int、String、list、Map等),对于复杂结构可以嵌套,主要访问接口如下 :
List<Object> getAll();
Object get(int fieldIndex);
Object get(String fieldName, HCatSchema recordSchema)
通过列名称来访问具体列的值,但是需要table的schema信息HCatSchema(具体每列的colName以及每个colName对应的fieldIndex)
这点设计不是很好,感觉可以将HCatSchema内置到HCatRecord 中,这样封装性可能会更好一些 、
4. HCatalog的元数据存储
HCatalog存储元数据和Hive的元数据是一致的,对于元数据的存储底层也是调用hive底层HiveMetaStoreClient的接口(我们常用的Hive其实是在这个类上封装了一层),其提供对于元数据库的各种操作(creat drop alter load insert等等 )
直接调用Hive接口/hive的客户端 写入的元数据在HCatalog中可以直接用,同样HCatalog写入的元数据以及数据在hive中也是可以直接使用的。
5. HCatInputFormat:读取HCatalog中数据的接口
读取的时候需要dbName、tableName、partition(这个可以没有)、tableProperties(可以没有)信息,存储于InputJobInfo中,InputJobInfo是读取时候的信息存储封装,在jobclient端初始化以后,使用Java的序列化Serializable转换成byte[],然后将byte[]编码成一个字符串存储(参见HCatUtil的encodeBytes和decodeBytes方法)到hadoop的配置Configuration来在整个mr任务中传递。
InputJobInfo初始化创建的时候只需要dbName、tableName、partition(这个可以没有)、tableProperties(可以没有)信息,在InitializeInput.getInputJobInfo方法中去连接元数据库,补齐InputJobInfo中的HCatTableInfo、partitions信息,对于不是分区的table,构造一个partition信息,将相应数据存储在里面。
getSplits(JobContext jobContext)方法,从conf配置中反序列化出InputJobInfo信息,然后根据partition中的信息,利用inputFormatClass、location来split成真正的底层数据的split,在封装成HCatSplit。
HCatSplit结构
最终读取的时候还是要利用PartInfo中的SerDe来读取相应的数据,后面介绍
createRecordReader(InputSplit split, TaskAttemptContext taskContext)方法
返回的是封装了一层的HCatRecordReader对象,然后通过HCatRecordReader. Initialize(split,taskContext)来初始化具体的读信息baseRecordReader等 。
一切初始化完毕,就可以开始调用经典的nextKeyValue的方法了
baseRecordReader是底层的RecordReader ,读出来数据后使用相应的SerDe的deserialize方法反序列化成ObjectInspector(一般是StructObjectInspector)可以访问的数据格式,然后通过相应的colnum ObjectInspector得到每列的具体Java object值,然后封装到HCatRecord对象中 。
对于hive基本类型转换成Java的基本类型,对于struct、list转换成Java的List,map转换成Java的map。
6. Hcatalog在MR程序中的输出类HCatOutputFormat
和input对应的,OutputJobInfo是存储用于输出数据时候的信息的,其结构如下:
主要是table的相关信息
OutputJobInfo outPutInfo=OutputJobInfo.create("analyse_db","contline_revenue_day_cut", partitions);
也是需要dbName、tblName、partition就可以初始化,通过HCatOutputFormat.setOutput(conf, credentials,outPutInfo)中连接metastore,然后补充其余的schema、Serde、outputFormat等一系列信息 ,目前Hcata中对于hive的自建Index、内置压缩、分桶和sort的特性还是不支持的,然后同样在利用序列化这个对象成String,存储与MR的配置conf中 ,提供给以后的程序共享使用。
getRecordWriter(TaskAttemptContext context)方法
先得到一个封装后的OutputFormatContainer,这个其实就是内部包装了一个这个table数据真正的OutputFormat对象,以FileOutputFormatContainer为例,以下是FileOutputFormatContainer. getRecordWriter(TaskAttemptContext context)方法
主要是做了一些MR程序中输出文件夹初始化的工作,然后就是返回了一个FileRecordWriterContainer对象,该对象和上面的OutputFormat一样,内部封装了一个table实际存储的OutputFormat返回的RecordWriter baseWriter ,最终真正用于向hdfs写数据的对象,FileRecordWriterContainer如下:
其中初始化了几个变量
serDe :用于序列化写到hdfs的数据
objectInspector :用于访问HCatRecord的 。
这里面还是利用了hive中的这一套Serde和ObjectInspector的封装来做的 。
7. 单机关于读/写的两个Java demo
write
1 package com.baidu.rigel.hactalog; 2 3 import java.io.IOException; 4 import java.util.HashMap; 5 import java.util.Map; 6 7 import org.apache.hadoop.conf.Configuration; 8 import org.apache.hadoop.io.WritableComparable; 9 import org.apache.hadoop.mapreduce.Job; 10 import org.apache.hadoop.mapreduce.OutputCommitter; 11 import org.apache.hadoop.mapreduce.RecordWriter; 12 import org.apache.hadoop.mapreduce.TaskAttemptContext; 13 import org.apache.hadoop.mapreduce.TaskAttemptID; 14 import org.apache.hive.hcatalog.data.DefaultHCatRecord; 15 import org.apache.hive.hcatalog.data.HCatRecord; 16 import org.apache.hive.hcatalog.data.schema.HCatFieldSchema.Type; 17 import org.apache.hive.hcatalog.data.schema.HCatSchema; 18 import org.apache.hive.hcatalog.mapreduce.FileOutputCommitterContainer; 19 import org.apache.hive.hcatalog.mapreduce.HCatOutputFormat; 20 import org.apache.hive.hcatalog.mapreduce.OutputJobInfo; 21 22 public class HcataLogWriteTestMain { 23 24 public static void main(String[] args) throws IOException, InterruptedException { 25 Map<String,String> partitions = new HashMap<String, String>(1); 26 partitions.put("pdate", "2011-09-24"); 27 Configuration conf =new Configuration(); 28 Job job = new Job(conf, "GroupByAge"); 29 OutputJobInfo outPutInfo=OutputJobInfo.create("analyse_db","contline_revenue_day_cut", partitions); 30 HCatOutputFormat.setOutput(job, outPutInfo); 31 HCatSchema s = HCatOutputFormat.getTableSchema(job.getConfiguration()); 32 System.out.println("INFO: output schema explicitly set for writing:" 33 + s); 34 //同样,序列化,然后设置到conf配置中 35 HCatOutputFormat.setSchema(job, s); 36 HCatOutputFormat outFormat= new HCatOutputFormat(); 37 // attempt_20140328173000_500575_m000_000000_0 task_20131314_0758_r_000100 38 job.getConfiguration().set("mapred.task.partition", "1"); 39 TaskAttemptID tmp= new TaskAttemptID("20140328173000",500575,true,0,0); 40 job.getConfiguration().set("mapred.task.id", "attempt_20140328173000_500575_m_000000_0"); 41 TaskAttemptContext taskTmp=new TaskAttemptContext(job.getConfiguration(),tmp); 42 RecordWriter<WritableComparable<?>, HCatRecord> hcataWrite=outFormat.getRecordWriter(taskTmp); 43 hcataWrite.close(null); 44 for(int i=0;i<100;i++){ 45 HCatRecord record = new DefaultHCatRecord(s.getFields().size()); 46 for(int k=0;k<s.getFields().size();k++){ 47 if(s.get(k).getType()==Type.BIGINT) 48 record.set(k, Long.parseLong(i+"")); 49 else if(s.get(k).getType()==Type.STRING) 50 record.set(k, i+"_ddctest"); 51 } 52 hcataWrite.write(null, record); 53 } 54 hcataWrite.close(taskTmp); 55 56 // System.out.println(outPutInfo.getDatabaseName()); 57 // System.out.println("******************************************************"); 58 // System.out.println(outPutInfo.getHarRequested()); 59 // System.out.println("******************************************************"); 60 // System.out.println(outPutInfo.getLocation()); 61 // System.out.println("******************************************************"); 62 // System.out.println(outPutInfo.getMaxDynamicPartitions()); 63 // System.out.println("******************************************************"); 64 // System.out.println(outPutInfo.getTableName()); 65 // System.out.println("******************************************************"); 66 // System.out.println(outPutInfo.getOutputSchema()); 67 // System.out.println("******************************************************"); 68 // System.out.println(outPutInfo.getPartitionValues()); 69 // System.out.println("******************************************************"); 70 // System.out.println(outPutInfo.getProperties()); 71 // System.out.println("******************************************************"); 72 // System.out.println(outPutInfo.getTableInfo()); 73 } 74 75 }
readTest
1 package com.baidu.rigel.hactalog; 2 3 import java.io.IOException; 4 import java.util.List; 5 6 import org.apache.hadoop.conf.Configuration; 7 import org.apache.hadoop.io.WritableComparable; 8 import org.apache.hadoop.mapred.JobConf; 9 import org.apache.hadoop.mapreduce.InputSplit; 10 import org.apache.hadoop.mapreduce.Job; 11 import org.apache.hadoop.mapreduce.RecordReader; 12 import org.apache.hadoop.mapreduce.TaskAttemptContext; 13 import org.apache.hadoop.mapreduce.TaskAttemptID; 14 import org.apache.hive.hcatalog.common.HCatConstants; 15 import org.apache.hive.hcatalog.data.HCatRecord; 16 import org.apache.hive.hcatalog.data.schema.HCatSchema; 17 import org.apache.hive.hcatalog.mapreduce.HCatInputFormat; 18 19 public class HcataLogReadTestMain { 20 21 public static void main(String[] args) throws IOException, InterruptedException { 22 Configuration conf = new Configuration(); 23 JobConf job=new JobConf(conf); 24 // InputJobInfo inputInfo=InputJobInfo.create("analyse_db", "contline_revenue_day","pdate=\"2014-03-09\"",new Properties()); 25 HCatInputFormat hIntput=HCatInputFormat.setInput(job, "analyse_db", "contline_revenue_day"); 26 hIntput.setFilter("pdate=\"2014-03-09\""); 27 Job jobContext=new Job(job); 28 System.out.println(job.get(HCatConstants.HCAT_KEY_JOB_INFO)); 29 hIntput=new HCatInputFormat(); 30 List<InputSplit> splitList=hIntput.getSplits(jobContext); 31 HCatSchema hCatSchema=HCatInputFormat.getTableSchema(job); 32 System.out.println(splitList.size()); 33 TaskAttemptID tmp= new TaskAttemptID("20131314",758,false,100,200); 34 TaskAttemptContext taskTmp=new TaskAttemptContext(job,tmp); 35 RecordReader<WritableComparable, HCatRecord> reader=hIntput.createRecordReader(splitList.get(0), taskTmp); 36 reader.initialize(splitList.get(0), taskTmp); 37 int cout=0 ; 38 while(reader.nextKeyValue()){ 39 HCatRecord row=reader.getCurrentValue(); 40 System.out.println(row.get("click_amt", hCatSchema)+" "+row.get("pdate", hCatSchema)); 41 cout++ ; 42 } 43 System.out.println(cout); 44 } 45 46 }