利用Spark往Hive中存储parquet数据,针对一些复杂数据类型如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';

分别向t1、t2执行insert into(insert overwrite..select也会导致下列问题)语句,列map_col都存储为空map:

insert into table t1 values(1,map(),array('1,1,1'),named_struct('A','1','B','1'));

insert into table t2 partition(dt='20200101') values(1,map(),array('1,1,1'),named_struct('A','1','B','1'));

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是分区表,仅仅从报错信息是无法看出表分区产生这种问题的原因,看看源码是做了哪些不同的处理(这里为了方便,笔者这里直接给出分析这个问题的源码思路图):

t1底层存储指定的是ParquetFilemat,t2底层存储指定的是HiveFileFormat。这里主要分析一下存储空map到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等数据类型做的处理,这里不在贴出 ....

可以看到在startFiled中首先对emptyField设置为true,只有在结束时比如endField方法中将emptyField设置为false,才不会抛出上述异常。而存储字段类型为map时,有几种情况会导致这种异常的发生,比如map为空或者map的key为null。

这里只是以map为例,对于array、struct都有类似问题,看源码HiveFileFormat -> DataWritableWriter对这三者处理方式类似。类似的问题,在Hive的issue中https://issues.apache.org/jira/browse/HIVE-11625也有讨论。

分析出问题解决就比较简单了,以存储map类型字段为例:

1. 如果无法改变建表schema,或者存储时底层用的就是HiveFileFormat

如果无法确定存储的map字段是否为空,存储之前判断一下map是否为空,可以写个udf或者用size判断一下,同时要保证key不能为null

2. 建表时使用Spark的DataSource表

-- 这种方式本质上还是用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

比如,ds.write.format("parquet").save("/tmp/test")其实像这类问题,相信很多人都遇到过并且解决了。这里是为了给出当遇到问题时,解决的一种思路。不仅要知道如何解决,更要知道发生问题是什么原因导致的、如何避免这种问题、解决了问题是怎么解决的(为什么这种方式能解决,有没有更优的方法)等。

近期文章:

Spark SQL解析查询parquet格式Hive表获取分区字段和查询条件

Spark SQL

Apache Hive


关注微信公众号:大数据学习与分享,获取更对技术干货

Spark存储Parquet数据到Hive,对map、array、struct字段类型的处理的更多相关文章

  1. spark 将dataframe数据写入Hive分区表

    从spark1.2 到spark1.3,spark SQL中的SchemaRDD变为了DataFrame,DataFrame相对于SchemaRDD有了较大改变,同时提供了更多好用且方便的API.Da ...

  2. spark读取mongodb数据写入hive表中

    一 环境: spark-: hive-; scala-; hadoop--cdh-; jdk-1.8; mongodb-2.4.10; 二.数据情况: MongoDB数据格式{    "_i ...

  3. 【原创】大叔问题定位分享(16)spark写数据到hive外部表报错ClassCastException: org.apache.hadoop.hive.hbase.HiveHBaseTableOutputFormat cannot be cast to org.apache.hadoop.hive.ql.io.HiveOutputFormat

    spark 2.1.1 spark在写数据到hive外部表(底层数据在hbase中)时会报错 Caused by: java.lang.ClassCastException: org.apache.h ...

  4. 【大数据】Hive学习笔记

    第1章 Hive基本概念 1.1 什么是Hive Hive:由Facebook开源用于解决海量结构化日志的数据统计. Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表, ...

  5. spark 性能优化 数据倾斜 故障排除

    版本:V2.0 第一章       Spark 性能调优 1.1      常规性能调优 1.1.1   常规性能调优一:最优资源配置 Spark性能调优的第一步,就是为任务分配更多的资源,在一定范围 ...

  6. Spark调优 数据倾斜

    1. Spark数据倾斜问题 Spark中的数据倾斜问题主要指shuffle过程中出现的数据倾斜问题,是由于不同的key对应的数据量不同导致的不同task所处理的数据量不同的问题. 例如,reduce ...

  7. spark调优——数据倾斜

    Spark中的数据倾斜问题主要指shuffle过程中出现的数据倾斜问题,是由于不同的key对应的数据量不同导致的不同task所处理的数据量不同的问题. 例如,reduce点一共要处理100万条数据,第 ...

  8. 利用SparkSQL(java版)将离线数据或实时流数据写入hive的用法及坑点

    1. 通常利用SparkSQL将离线或实时流数据的SparkRDD数据写入Hive,一般有两种方法.第一种是利用org.apache.spark.sql.types.StructType和org.ap ...

  9. Spark操作parquet文件

    package code.parquet import java.net.URI import org.apache.hadoop.conf.Configuration import org.apac ...

随机推荐

  1. K8s之实践Pod深入理解

      K8s之实践Pod深入理解 1.同一pod下的nginx+php+mysql nginx+php+mysql.yaml文件 --- apiVersion: v1 kind: Secret meta ...

  2. 【SpringCloud】06.Eureka 总结

    1.两个注解: @EnableEurekaServer--在启动类上添加 @EnableDiscoveryClient或@EnableEurekaClient--启动类加 因为Eureka支持多种注册 ...

  3. nginx vhost配置

    server { listen 80; server_name crsdemo.my; index index.html index.htm index.php default.html defaul ...

  4. python3批量修改文件后缀名

    import os # 原文件后缀名 suffix_name = '.jar.src.zip' # 新文件后缀名 nwe_suffix_name = '.jar' def foo(path1): fi ...

  5. 学习笔记——ESP8266项目的例子编译时发生cannot find -lstdc++问题的解决

    在尝试对进行ESP8266项目的例子进行编译时发生cannot find -lstdc++问题 第一想法是安装libstdc++,结果安装时又发生了下面的情况: 再次查找原因,最后发现当前安装的交叉编 ...

  6. null列的值不参与比较

    假如数据库有表emp: empno name   age 001      lucy      22 002      lily        null 003      lilei       nu ...

  7. tcp syn-synack-ack 服务端发送syn-ack

    tcp_v4_send_synack()用于发送SYNACK段,在tcp_v4_conn_request()中被调用. 首先调用tcp_make_synack()构造SYNACK段,主要是构造TCP报 ...

  8. linux中几个文本文件查看命令

    Linux中,常用的文本文件查看命令介绍如下: 1. cat 用法: cat [options] filename options: -A: 显示全部. -E: 在每一行的后面加上"$&qu ...

  9. Spring源码之@Lazy和预实例化

    https://www.cnblogs.com/yanze/p/10243348.html 懒加载优缺点 优点:懒加载,对象使用的时候才去创建:启动速度快,节省资源 缺点:不利于提前发现错误:初次请求 ...

  10. JAVA注解的继承性

    摘要 本文从三个方面介绍java注解的**"继承性"**: 基于元注解@Inherited,类上注解的继承性 基于类的继承,方法/属性上注解的继承性 基于接口的继承/实现,方法/属 ...