現在的場景是需要將ElasticSearch中的數據導入到Hive中,但是在導入的時候發現了日期映射的異常,ElasticSearch中日期字段定義的格式為:
time:{
type:date,
store:true,
include_in_all:true,
format:yyyy-MM-ddHH:mm:ss
}
在Hive中建立外部表如下:
CREATEEXTERNALTABLE`test_table_es`(
`meta_id`stringCOMMENT'fromdeserializer',
......
`time`TIMESTAMP)
ROWFORMATSERDE
'org.elasticsearch.hadoop.hive.EsSerDe'
STOREDBY
'org.elasticsearch.hadoop.hive.EsStorageHandler'
WITHSERDEPROPERTIES(
'serialization.format'='1')
TBLPROPERTIES(
'COLUMN_STATS_ACCURATE'='false',
'es.index.auto.create'='false',
'es.mapping.names'='meta_id:_metadata._id,time:time',
'es.nodes'='127.0.0.1:9200',
'es.read.metadata'='true',
'es.resource'='test_index/test_type');
建立外部表的時候,不會報錯,但是查詢的時候會報日期不能夠正常映射為Hive中的Timestamp格式。
elasticsearch-hadoop中用於將ES中的日期轉換為Hive中的日期格式的類為org.elasticsearch.hadoop.hive.HiveValueReader,通過查看該類的源碼,其實現的用戶日期轉換的方法為:
@Override
protectedObjectparseDate(Stringvalue,booleanrichDate){
return(richDate?newTimestampWritable(newTimestamp(DatatypeConverter.parseDateTime(value).getTimeInMillis())):parseString(value));
}
可以看到它是通過javax.xml.bind.DatatypeConverter.parseDateTime(String)方法將對應的日期字符串轉換為日期的,
該方法不支持的日期字符串格式為“yyyy-MM-dd HH:mm:ss”的字符串,它支持的日期字符串的格式為“yyyy-MM-ddTHH:mm:ss”這樣的。
因而為了支持這種轉換,可以選擇兩種處理方式:
一是修改原始數據,二是在轉換的過程中做數據轉換,考慮到第一種方式要處理非常多的數據,因而採用了第二種方式,實現自己的ValueReader,在實現ValueReader的時候,要考慮兼容其它的日期格式,並且只處理屬性中指定了日期格式為“yyyy-MM-dd HH:mm:ss”的日期轉換,其它日期格式還是採用默認的方式處理,這樣才能夠兼容其它的日期格式,否則會導致其它的日期格式出錯,以下是一個實現的自定義的EsValueReader的原碼:
packagecom.service.hadoop;
importjava.sql.Timestamp;
importjava.text.ParseException;
importjava.text.ParsePosition;
importjava.text.SimpleDateFormat;
importjava.util.Calendar;
importjava.util.Date;
importjavax.xml.bind.DatatypeConverter;
importorg.apache.hadoop.hive.serde2.io.TimestampWritable;
importorg.elasticsearch.hadoop.cfg.Settings;
importorg.elasticsearch.hadoop.hive.HiveValueReader;
importcom.sun.xml.bind.DatatypeConverterImpl;
/**
*類EsValueReader.java的實現描述:用於轉換ES中的日期類型,用於匹配Hive中的日期類型
*
*@authorfenglibin2018年4月19日下午3:54:00
*/
publicclassEsValueReaderextendsHiveValueReader{
privateStringdateFormat;
privatestaticfinalStringDEFALUT_DATE_FORMAT=yyyy-MM-ddHH:mm:ss;
@Override
publicvoidsetSettings(Settingssettings){
super.setSettings(settings);
dateFormat=settings.getProperty(es.date.format);
}
@Override
protectedObjectparseDate(Stringvalue,booleanrichDate){
if(value!=nullvalue.trim().length()0DEFALUT_DATE_FORMAT.equalsIgnoreCase(dateFormat)){
return(richDate?newTimestampWritable(newTimestamp(parseDate(value,
DEFALUT_DATE_FORMAT).getTime())):parseString(value));
}
/**如果沒有設置日期格式,通過默認的方式支持,以避免使用新的ValueReader後影響到其它的外部表**/
returnsuper.parseDate(value,richDate);
}
/**
*解析日期,根據指定的格式進行解析.br
*如果解析錯誤,則返回null
*@paramstringDate日期字符串
*@paramformat日期格式
*@return日期類型
*/
privatestaticDateparseDate(StringstringDate,Stringformat){
if(stringDate==null){
returnnull;
}
try{
returnparseDate(stringDate,newString[]{format});
}catch(ParseExceptione){
returnnull;
}
}
publicstaticDateparseDate(Stringstr,String...parsePatterns)throwsParseException{
returnparseDateWithLeniency(str,parsePatterns,true);
}
privatestaticDateparseDateWithLeniency(
Stringstr,String[]parsePatterns,booleanlenient)throwsParseException{
if(str==null||parsePatterns==null){
thrownewIllegalArgumentException(DateandPatternsmustnotbenull);
}
SimpleDateFormatparser=newSimpleDateFormat();
parser.setLenient(lenient);
ParsePositionpos=newParsePosition(0);
for(StringparsePattern:parsePatterns){
Stringpattern=parsePattern;
//LANG-530-needtomakesure'ZZ'outputdoesn'tgetpassedtoSimpleDateFormat
if(parsePattern.endsWith(ZZ)){
pattern=pattern.substring(0,pattern.length()-1);
}
parser.applyPattern(pattern);
pos.setIndex(0);
Stringstr2=str;
//LANG-530-needtomakesure'ZZ'outputdoesn'thitSimpleDateFormatasitwillParseException
if(parsePattern.endsWith(ZZ)){
str2=str.replaceAll(([-+][0-9][0-9]):([0-9][0-9])$,$1$2);
}
Datedate=parser.parse(str2,pos);
if(date!=nullpos.getIndex()==str2.length()){
returndate;
}
}
thrownewParseException(Unabletoparsethedate:+str,-1);
}
}
將這個類導出一個jar包並將其加到Hive的auxlib目錄,然後再重啟Hive即可。
閱讀更多 加米穀大數據 的文章