考虑这么个场景:某天你leader过来跟你说,我这现在有大量文本文件,大小不一,大的好几十G,小的几百兆;现在想提取出来共有的几个业务字段做分析,但是每种类型的接口文件的字段解析规则还不一样,比如业务字段A,在file1里面在第10字节到18字节之间,在file2中在第8字节到第16字节之间,问你怎么办?

如果有现成的hdfs集群,其实这事儿用Hive自定义文件读取这个特性会非常省事儿!

hive-serde

文件如何读取(inputformat),读取的内容如何解析(serde

你会发现,你只需要实现几个简单的类,篇头问题即迎刃而解

Inputformat

  • 既然处理文本,当然org.apache.hadoop.mapred.TextInputFormat
  • 只需实现接口,把Split传给RecordReader,具体文本切分规则交给RecordReader

XFileInputFormat

RecordReader

  • 主要逻辑在next()中,通过LineRecordReader解析Split,然后生成原始row
public boolean next(LongWritable key, Text value) throws IOException {
while (true) {
// get current line
if (!this.reader.next(this.lineKey, this.lineValue)) {
return false;
}
key.set(key.get() + 1);
lineValue.append(byteOfSplitName, 0, byteOfSplitName.length);
value.set(this.lineValue.getBytes());
//Text.validateUTF8(value.getBytes());
return true;
}
}

XRecordReader

Serde

  • 负责把每一个原生row根据业务规则解析成多个field,这才产出一条有意义的record
  • initialize()初始化,根据表schema定义,确定好有哪些列,每一列都是什么类型
  • 我们只想读取,所以只需实现deserialize()接口
  • ObjectInspector很精髓!

XSerDe

Hive Sql 定义

hive -e "CREATE EXTERNAL TABLE IF NOT EXISTS khala_his.dwd_acco_his
(
c_clientid STRING,
c_custname STRING,
d_datadate DATE
...
)
PARTITIONED BY (year INT, month INT, day INT)
ROW FORMAT SERDE 'com.michael.hive.XSerDe'
STORED AS
INPUTFORMAT 'com.michael.hive.XFileInputFormat'
OUTPUTFORMAT 'com.michael.hive.XFileOutputFormat'
LOCATION '/data/khala/his/dwd_acco_his'

Answer

说到这,还有个关键问题没解决,如何解决同一表schema适配不同文本文件呢?只需两步

  • RecordReader把文件名split.getPath().getName()拼到每一个row末尾,相当于加一个虚拟列,用来索引对应row解析规则
  • SerDe解析字段规则时,根据虚拟列获取到解析规则,然后解析即可

org.apache.hadoop.io.Text标准UTF8编码,如果你的文本不是UTF8,生成row时要用getBytes(),非toString()
解析类型时候,DECIMAL要用HiveDecimal.create(BigDecimal decimal),DATE用java.sql.Date()

Reference