一、背景介绍

在前一篇文章TDengine在Apache Hive中的探索和实践中,我们通过对2.3.8版本Apache Hive的代码改造,使之支持查询TDengine数据源的数据,并支持关联查询多个不同数据源。

在使用过程中,我们陆续发现了一些问题,本文内容就是其中之一,特此记录下。

二、问题背景

我们在Hive创建了4张表,表名分别为fvp、dn、enpoint、student。

1.fvp和dn是TDengine类型的表 2.enpoint是Mysql类型的表

这三个表都是使用JdbcStorageHandler创建的。通过上一篇博文的分析,可知其底层对应的InputFormat都为JdbcInputFormat。

3.student是关联HDFS数据的表

由于是原生的Hive表,所以对应的InputFormat为TextInputFormat。

三、问题场景

在hive中执行如下sql,一共关联了上述4张表

select count(*) as count from fvp inner join endpoint on fvp.source_zone_code=endpoint.endpoint inner join student on student.name=fvp.source_zone_code inner join dn on fvp.source_zone_code=dn.fqdn  where fvp.source_zone_code='915J'

上述sql运行结束后,hive控制台偶尔报错,偶尔执行成功。

每次报错时截图如下

hive命令行如何更改日志级别 hive yarn日志_hive命令行如何更改日志级别

四、查看和分析YARN日志

因为Hive底层会生成MR任务执行在YARN上,所以我们去YARN上查看详细日志。 访问IP:19888/jobhistory查看job列表,找到刚才执行的job。

1.查看日志

点击job id

hive命令行如何更改日志级别 hive yarn日志_其他_02

由于我们设置了set mapred.map.tasks=22,所以此处生成了22个MapTask。

点进去

hive命令行如何更改日志级别 hive yarn日志_hive命令行如何更改日志级别_03

我们找其中一个失败的MapTask,点击进去看看

hive命令行如何更改日志级别 hive yarn日志_其他_04

继续点击logs

hive命令行如何更改日志级别 hive yarn日志_其他_05

日志如下,但是只展示了部分。点击here按钮查看详细日志

hive命令行如何更改日志级别 hive yarn日志_Hive_06

详细日志如下

hive命令行如何更改日志级别 hive yarn日志_tdengine_07

2.日志解读

通过最后的日志截图,我们发现,当前处理失败的MapTask对应的切片是student表切片hdfs://CNSZ22PL0272:9000/user/hive/warehouse/student/student.txt:9+

前面说过,student表是HDFS上的数据,底层对应的InputFormat为TextInputFormat。

而观察截图最后的报错信息,可以看到是由TDengineDatabaseAccessor类(我自己在源码中写的类)中的getColumnNames方法抛出的,该方法里会使用JDBC-RESTful方式来获取TDengine表的字段。

伪逻辑如下:

//通过DBCP数据源获取连接
......

//切换到数据库
stmt.executeUpdate("use 数据库名");

//查询
stmt.executeQuery("select 字段1,字段2...字段n from 表名");

由此,引发的困惑是:

为什么Hive在处理HDFS数据源的数据时,会涉及到TDengine数据源的代码?难道是多个数据源关联时,底层的InputFormat会共用?

五、问题分析

1.确定InputFormat类与切片是否对应

这一步排查思路是,排查当多个不同数据源进行连接查询时,Hive能否根据不同数据源,找到正确的InputFormat。

当Hive解析sql后,会根据生成的物理执行计划提交MR任务,MR任务根据InputFormat生成切片。

所以我们先找到生成切片的代码。

hive命令行如何更改日志级别 hive yarn日志_hive_08

进入writeSplits方法后,最后进入如下逻辑,其中 job.getInputFormat()是CombineHiveInputFormat

hive命令行如何更改日志级别 hive yarn日志_其他_09

于是我们看看CombineHiveInputFormat的方法

hive命令行如何更改日志级别 hive yarn日志_Hive_10

其中生成切片的过程很长,涉及到JdbcInputFormat和TextInputFormat的代码,但此处我们不关心。

我们只关心:

每个数据源的MapTask和其InputFormat是否对应得上

最终返回的切片,如下

hive命令行如何更改日志级别 hive yarn日志_Hive_11

观察生成的切片,结论如下:

我们有4个数据源的表,在没有在HIve中手动设置mapred.map.tasks参数的情况下,Hive最终生成了5个切片(其中student占2个)。更重要的是,每个数据源的切片信息和其InputFormat是能对应得上的。

这说明Hive是可以根据不同数据源,找到正确的InputFormat的。

2. 加日志分析

既然每个数据源的切片信息和其InputFormat都能对应得上,说明多数据源关联时不会出现混淆的问题,那么只能进一步分析了。

通过上面YARN的日志

hive命令行如何更改日志级别 hive yarn日志_hive_12

发现每个MapTask类会调用到MapOperator的getConvertedOI方法,该方法中通过getDeserializer方法触发了TDengine相关类的代码

hive命令行如何更改日志级别 hive yarn日志_tdengine_13

所以,我们可以尝试在getConvertedOI方法中加入日志,打印当前表名和切片信息

hive命令行如何更改日志级别 hive yarn日志_其他_14

3. 构建源码包并重新上传

我是在Hive项目的hive-exec模块的MapOperaor类中加入的日志代码,为了让代码在MR任务运行时生效,需要重新上传jar。

首先,使用maven构建hive-exec模块,并在Hive的lib目录下重新上传。我的hive的lib路径为

/app/hive/apache-hive-2.3.8-bin/lib

其次,还需要在hadoop的lib目录里重新上传(因为是分布式集群环境)。我的Hadoop的lib路径为

/app/hadoop/hadoop-3.3.0/share/hadoop/common/lib

当这些工作完成后,重新启动Hive。

六、再次分析

1.查看日志

我们再次运行同样的sql,按照同样步骤观察YARN某个MapTask任务的日志

hive命令行如何更改日志级别 hive yarn日志_Hive_15

可以发现如下重要信息:

在任务处理切片依旧为student表切片的前提下,getConvertedOI方法一共调用了4次,每个表一次。 在最后一次处理dn表信息时,抛出了异常(开头提到dn表是TDengine类型的表)。

2.最终的误解

通过上面信息,说明最开始就是我们误解了:

当多数据源关联查询时,每个MapTask都会去获取其他表的信息,这是Hive本身正常的逻辑,而不是当初我认为的InputFormat混淆。

而后来借助更详细的日志,才及时帮我们发现了这种误解。 之所以耗费了这么大精力去证明这是个误会,还是因为我对Hive底层执行逻辑不了解导致的。

所以,解决方案是要搞明白为什么使用JDBC-RESTful方式来获取TDengine表的字段时会报错,而不是从Hive自身的逻辑里找答案。

七、问题解决

我使用的JDBC-RESTful方式来获取TDengine表字段,伪逻辑如下

//通过DBCP数据源获取连接
......

//切换到数据库
stmt.executeUpdate("use 数据库名");

//查询
stmt.executeQuery("select * from 表名");

在将该问题反馈给涛思公司的工程师后,得到的回复是

有可能是因为使用dbcp连接池,去不同的database查造成的。在查询sql中指定dbName应该就可以解决这个问题。

最终我使用如下方式解决了问题

//通过DBCP数据源获取连接
......

//查询
stmt.executeQuery("select * from 数据库名.表名");