在测试hive的load性能时,我们在建表时指定使用|
作为分隔符。这样就需要考虑一个问题:如果外部文本中|
作为文本内容出现时,如何区分到底是分隔符还是文本内容。
首先测试hive是否能智能区分分隔符与文本内容,结果表明:当分隔符与文本内容相同时,会产生数据混淆问题。
SQL如何解决这一问题
参考链接:如何在sqlldr中导入多字符分隔符文件
SQL中对此的解决办法是采用多字符分隔符,以降低产生数据混淆的概率。
Hive中对这一问题的解决思路与SQL相同
然而hive中默认只支持单字符分割符,如果指定多字符分隔符将会报错。
参考链接:hive如何处理多分隔符数据
hive处理日志,自定义inputformat
hive创建表指定分隔符,不支持多个字符作为分隔符
解决办法:
- 利用hive自带的序列化/反序列化的方式RegexSe
- 重写相应的InputFormat和OutputFormat方法
1. 利用hive自带的序列化/反序列化的方式RegexSe
这种方式稍微复杂一点,对数据的控制能力也要弱一些,它使用正则表达式来匹配和处理数据,性能也会有所影响。但它的优点是可以自定义表属性信息 SERDEPROPERTIES ,在 SerDe 中通过这些属性信息可以有更多的定制行为。
/*样例数据*/
110|#警察
120|#医院
/*建表*/
add jar /home/cup/software/……/hive-contrib-0.10.0-cdh4.4.0.jar;
create table test
(
id string,
name string
)partitioned by (c_day string)
row format serde 'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'
with serdeproperties
( 'input.regex' = '([^\\|#]*)\\|#([^\\|#]*)' , 'output.format.string' = '%1$s%2$s')
stored as textfile;
/*load*/
load data local inpath '/……/test.txt' overwrite into table test partition(c_day = '20141027');
/*查询结果*/
select * from test;
110 警察 20141027
120 医院 20141027
2. 自定义 outputformat 和 inputformat
Hive 的 outputformat/inputformat 与 hadoop 的 outputformat/inputformat 相当类似, inputformat 负责把输入数据进行格式化,然后提供给 Hive,outputformat 负责把 Hive 输出的数据重新格式化成目标格式再输出到文件,这种对格式进行定制的方式较为底层,对其进行定制也相对简单,重写 InputFormat 中 RecordReader 类中的 next 方法即可。
/*样例数据*/
2010-05-31 10:50:17|||61.132.4.82|||http://www.360buy.com/product/201185.html
/*分隔符是“ ||| ”,这是为了尽可能防止日志正文出现与分隔符相同的字符而导致数据混淆。 hive 的内部分隔符是“ \001 ”,所以我们需要做一下转换 */
/*编写自定义InputFormat */
package com.jd.cloud.clickstore;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.FileSplit;
import org.apache.hadoop.mapred.InputSplit;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.JobConfigurable;
import org.apache.hadoop.mapred.RecordReader;
import org.apache.hadoop.mapred.Reporter;
import org.apache.hadoop.mapred.TextInputFormat;
/**
* 自定义hadoop的 org.apache.hadoop.mapred.InputFormat
*
* @author winston
*
*/
public class ClickstreamInputFormat extends TextInputFormat implements
JobConfigurable {
public RecordReader<LongWritable, Text> getRecordReader(
InputSplit genericSplit, JobConf job, Reporter reporter)
throws IOException {
reporter.setStatus(genericSplit.toString());
return new ClickstreamRecordReader(job, (FileSplit) genericSplit);
}
}
/*自定义ClickstreamRecordReader实现RecordReader接口,并重写next方法 */
/** Read a line. */
public synchronized boolean next(LongWritable key, Text value)
throws IOException {
while (pos < end) {
key.set(pos);
int newSize = in.readLine(value, maxLineLength,
Math.max((int)Math.min(Integer.MAX_VALUE, end-pos),
maxLineLength));
//start
String strReplace = value.toString().toLowerCase().replaceAll("\\|\\|\\|" , "\001" );
Text txtReplace = new Text();
txtReplace.set(strReplace );
value.set(txtReplace.getBytes(), 0, txtReplace.getLength());
//end
if (newSize == 0) {
return false;
}
pos += newSize;
if (newSize < maxLineLength) {
return true;
}
// line too long. try again
LOG.info("Skipped line of size " + newSize + " at pos " + (pos - newSize));
}
return false;
}
/*我们可以直接使用LineRecordReader,修改next方法 */
/*启动hive,添加我们自己刚刚添加的类 */
/*创建数据库*/
/*自定义 outputformat/inputformat 后,在建表时需要指定 outputformat/inputformat */
create table clickstream_table(time string, ip string, url string) stored as INPUTFORMAT 'com.jd.cloud.clickstore.ClickstreamInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat' LOCATION '/data/clickstream_20110216.txt';
/*LOAD数据 */
LOAD DATA LOCAL INPATH '/data/clickstream_20110216.txt' OVERWRITE INTO TABLE clickstream_table;
/*查询刚刚LOAD的数据*/
select * from clickstream_table;