您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Spark存儲Parquet數據到Hive時如何對map、array、struct字段類型進行處理的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
為了更好的說明導致問題的原因、現象以及解決方案,首先看下述示例:
-- 創建存儲格式為parquet的Hive非分區表
CREATE EXTERNAL TABLE `t1`(
`id` STRING,
`map_col` MAP<STRING, STRING>,
`arr_col` ARRAY<STRING>,
`struct_col` STRUCT<A:STRING,B:STRING>)
STORED AS PARQUET
LOCATION '/home/spark/test/tmp/t1';
-- 創建存儲格式為parquet的Hive分區表
CREATE EXTERNAL TABLE `t2`(
`id` STRING,
`map_col` MAP<STRING, STRING>,
`arr_col` ARRAY<STRING>,
`struct_col` STRUCT<A:STRING,B:STRING>)
PARTITIONED BY (`dt` STRING)
STORED AS PARQUET
LOCATION '/home/spark/test/tmp/t2';
insert into table t1 values(1,map(),array('1,1,1'),named_struct('A','1','B','1'));
insert into table t2 partition(dt='20200101')
t1表正常執行,但對t2執行上述insert語句時,報如下異常:
Caused by: parquet.io.ParquetEncodingException: empty fields are illegal, the field should be ommited completely instead
at parquet.io.MessageColumnIO$MessageColumnIORecordConsumer.endField(MessageColumnIO.java:244)
at org.apache.hadoop.hive.ql.io.parquet.write.DataWritableWriter.writeMap(DataWritableWriter.java:241)
at org.apache.hadoop.hive.ql.io.parquet.write.DataWritableWriter.writeValue(DataWritableWriter.java:116)
at org.apache.hadoop.hive.ql.io.parquet.write.DataWritableWriter.writeGroupFields(DataWritableWriter.java:89)
at org.apache.hadoop.hive.ql.io.parquet.write.DataWritableWriter.write(DataWritableWriter.java:60)
... 23 more
t1和t2從建表看唯一的區別就是t1不是分區表而t2是分區表,僅僅從報錯信息是無法看出表分區產生這種問題的原因,看看源碼是做了哪些不同的處理(這里為了方便,筆者這里直接給出分析這個問題的源碼思路圖):
從拋出的異常信息empty fields are illegal,關鍵看empty fields在哪里拋出,做了哪些處理,這要看MessageColumnIO中startField和endField是做了哪些處理:
public void startField(String field, int index) {
try {
if (MessageColumnIO.DEBUG) {
this.log("startField(" + field + ", " + index + ")");
}
this.currentColumnIO = ((GroupColumnIO)this.currentColumnIO).getChild(index);
//MessageColumnIO中,startField方法中首先會將emptyField設置為true
this.emptyField = true;
if (MessageColumnIO.DEBUG) {
this.printState();
}
} catch (RuntimeException var4) {
throw new ParquetEncodingException("error starting field " + field + " at " + index, var4);
}
}
//endField方法中會針對emptyField是否為true來決定是否拋出異常
public void endField(String field, int index) {
if (MessageColumnIO.DEBUG) {
this.log("endField(" + field + ", " + index + ")");
}
this.currentColumnIO = this.currentColumnIO.getParent();
//如果到這里仍為true,則拋異常
if (this.emptyField) {
throw new ParquetEncodingException("empty fields are illegal, the field should be ommited completely instead");
} else {
this.fieldsWritten[this.currentLevel].markWritten(index);
this.r[this.currentLevel] = this.currentLevel == 0 ? 0 : this.r[this.currentLevel - 1];
if (MessageColumnIO.DEBUG) {
this.printState();
}
}
}
針對map做處理的一些源碼:
private void writeMap(final Object value, final MapObjectInspector inspector, final GroupType type) {
// Get the internal map structure (MAP_KEY_VALUE)
GroupType repeatedType = type.getType(0).asGroupType();
recordConsumer.startGroup();
recordConsumer.startField(repeatedType.getName(), 0);
Map<?, ?> mapValues = inspector.getMap(value);
Type keyType = repeatedType.getType(0);
String keyName = keyType.getName();
ObjectInspector keyInspector = inspector.getMapKeyObjectInspector();
Type valuetype = repeatedType.getType(1);
String valueName = valuetype.getName();
ObjectInspector valueInspector = inspector.getMapValueObjectInspector();
for (Map.Entry<?, ?> keyValue : mapValues.entrySet()) {
recordConsumer.startGroup();
if (keyValue != null) {
// write key element
Object keyElement = keyValue.getKey();
//recordConsumer此處對應的是MessageColumnIO中的MessageColumnIORecordConsumer
//查看其中的startField和endField的處理
recordConsumer.startField(keyName, 0);
//查看writeValue中對原始數據類型的處理,如int、boolean、varchar
writeValue(keyElement, keyInspector, keyType);
recordConsumer.endField(keyName, 0);
// write value element
Object valueElement = keyValue.getValue();
if (valueElement != null) {
//同上
recordConsumer.startField(valueName, 1);
writeValue(valueElement, valueInspector, valuetype);
recordConsumer.endField(valueName, 1);
}
}
recordConsumer.endGroup();
}
recordConsumer.endField(repeatedType.getName(), 0);
recordConsumer.endGroup();
}
private void writePrimitive(final Object value, final PrimitiveObjectInspector inspector) {
//value為null,則return
if (value == null) {
return;
}
switch (inspector.getPrimitiveCategory()) {
//PrimitiveCategory為VOID,則return
case VOID:
return;
case DOUBLE:
recordConsumer.addDouble(((DoubleObjectInspector) inspector).get(value));
break;
//下面是對double、boolean、float、byte、int等數據類型做的處理,這里不在貼出
....
這里只是以map為例,對于array、struct都有類似問題,看源碼HiveFileFormat -> DataWritableWriter對這三者處理方式類似。類似的問題,在Hive的issue中https://issues.apache.org/jira/browse/HIVE-11625也有討論。
1. 如果無法改變建表schema,或者存儲時底層用的就是HiveFileFormat
-- 這種方式本質上還是用ParquetFileFormat,并且是內部表,生產中不建議直接使用這種方式
CREATE TABLE `test`(
`id` STRING,
`map_col` MAP<STRING, STRING>,
`arr_col` ARRAY<STRING>,
`struct_col` STRUCT<A:STRING,B:STRING>)
USING parquet
OPTIONS(`serialization.format` '1');
3. 存儲時指定ParquetFileFormat
感謝各位的閱讀!關于“Spark存儲Parquet數據到Hive時如何對map、array、struct字段類型進行處理”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。