问题背景
hive(2.3.9)组件集成hudi(0.11.0)后,查询hudi表偶现:java.lang.NoSuchMethodError:org.apache.hadoop.hdfs.client.HdfsDataInputStream.getReadStatistics()Lorg/apache/hadoop/hdfs/DFSInputStream$ReadStatistics。这个问题很奇怪,刚开始查询hudi表没有任何问题,同一张表后面再查询的时候就出现这个问题了。出于良好的职业道德,虽然测试还没有发现这个问题,我还是赶紧研究研究修复一下吧。
干活
错误日志:
从堆栈信息上看是在操作hfile的时候出错了,hudi-0.11.0版本自动开启了metadata表,这个表的存储格式是hfile,因此之前的版本可能遇不到这个问题。方法找不到,可能原因就是jar包版本冲突了呗,应该很好搞定吧,至少前期我是这么想的。
首先看下这个类org.apache.hadoop.hdfs.client.HdfsDataInputStream都出现在哪些jar包中,使用命令grep -rn ./hive/lib/搜索一下,发现没有找到。那可能是hdfs,tez等组件下有,最终是发现在tez下hadoop-hdfs-client-3.2.3.jar有这个类。只有这一个jar包有这个类,那它跟谁冲突呢,有点入坑的感觉了。
看下hadoop的代码,发现org.apache.hadoop.hdfs.client.HdfsDataInputStream.getReadStatistics()方法的返回值应该是org.apache.hadoop.hdfs.client.ReadStatistics,从报错信息上可以看到,它需要的返回值应该是DFSInputStream的内部类ReadStatistics,稍微动动脑子应该能够想到可能的原因是把org.apache.hadoop.hdfs.client.HdfsDataInputStream.getReadStatistics()赋值给DFSInputStream的内部类ReadStatistics了。去调用代码的地方看下就明白了。但是此时的我并没有动脑子,一心想着肯定是jar包版本冲突了,我一定要找到不同版本jar出来。唉,脑子是个好东西呀。。。
无用功
找了一圈也没有发现冲突的版本,可把我急坏了,于是想到了阿里的arthas工具,这个工具可以查看jvm中类是从哪里加载过来的。因为是内网环境,因此需要离线下载全量包,下载路径在这里:https://arthas.aliyun.com/download/latest_version?mirror=aliyun.下载之后上传到服务器上,解压,然后修改属主为hive,因为我们要查看的jvm是hiveserver2。
- 首先监视
org.apache.hudi.org.apache.hadoop.hbase.io.FsDataInputStreamWrapper.updateInputStreamStatistics方法,然后执行报错的sql触发异常。
监视命令:trace org.apache.hudi.org.apache.hadoop.hbase.io.FsDataInputStreamWrapper updateInputStreamStatistics
监视结果:
能看出来确实是被监视的方法抛出的异常,但是看不出来其他信息了。
- 看下
org.apache.hudi.org.apache.hadoop.hbase.io.FsDataInputStreamWrapper和org.apache.hadoop.hdfs.client.HdfsDataInputStream分别是从哪个jar包加载进来的
命令:sc -d org.apache.hudi.org.apache.hadoop.hbase.io.FsDataInputStreamWrapper
类加载路径没有问题,那就反编译看下代码吧,反编译命令:jad org.apache.hadoop.hdfs.client.HdfsDataInputStream,发现org.apache.hadoop.hdfs.client.HdfsDataInputStream.getReadStatistics()方法的返回值类型只有一个org.apache.hadoop.hdfs.client.ReadStatistics,到这里应该可以确定不是jar包冲突的问题了。所以就只能是上面说到的代码问题了。在仔细看下报错这里的代码吧。
雾里看花
private void updateInputStreamStatistics(FSDataInputStream stream) {
// If the underlying file system is HDFS, update read statistics upon close.
if (stream instanceof HdfsDataInputStream) {
/**
* Because HDFS ReadStatistics is calculated per input stream, it is not feasible to update
* the aggregated number in real time. Instead, the metrics are updated when an input stream
* is closed.
*/
HdfsDataInputStream hdfsDataInputStream = (HdfsDataInputStream) stream;
synchronized (readStatistics) {
readStatistics.totalBytesRead +=
hdfsDataInputStream.getReadStatistics().getTotalBytesRead();
readStatistics.totalLocalBytesRead +=
hdfsDataInputStream.getReadStatistics().getTotalLocalBytesRead();
readStatistics.totalShortCircuitBytesRead +=
hdfsDataInputStream.getReadStatistics().getTotalShortCircuitBytesRead();
readStatistics.totalZeroCopyBytesRead +=
hdfsDataInputStream.getReadStatistics().getTotalZeroCopyBytesRead();
}
}
}
调用hdfsDataInputStream.getReadStatistics().getTotalBytesRead()赋值给readStatistics.totalBytesRead,readStatistics是类的成员变量,是这么初始化的:
private final static ReadStatistics readStatistics = new ReadStatistics();
按住Ctrl键进入这个对象,咦~~~,这个ReadStatistics可不是org.apache.hadoop.hdfs.client.ReadStatistics呀,这里的ReadStatistics,是org.apache.hudi.org.apache.hadoop.hbase.io.FsDataInputStreamWrapper的内部类呀,破案了,破案了。于是修改下代码:
private void updateInputStreamStatistics(FSDataInputStream stream) {
// If the underlying file system is HDFS, update read statistics upon close.
if (stream instanceof HdfsDataInputStream) {
/**
* Because HDFS ReadStatistics is calculated per input stream, it is not feasible to update
* the aggregated number in real time. Instead, the metrics are updated when an input stream
* is closed.
*/
HdfsDataInputStream hdfsDataInputStream = (HdfsDataInputStream) stream;
synchronized (readStatistics) {
org.ahacpe.hadoop.hdfs.ReadStatistics readStatisticsHdfs = hdfsDataInputStream.getReadStatistics();
FSDataInputStreamWrapper.readStatistics.totalBytesRead +=
readStatisticsHdfs.getTotalBytesRead();
FSDataInputStreamWrapper.readStatistics.totalLocalBytesRead +=
readStatisticsHdfs.getTotalLocalBytesRead();
FSDataInputStreamWrapper.readStatistics.totalShortCircuitBytesRead +=
readStatisticsHdfs.getTotalShortCircuitBytesRead();
FSDataInputStreamWrapper.readStatistics.totalZeroCopyBytesRead +=
readStatisticsHdfs.getTotalZeroCopyBytesRead();
}
}
}
重新编译替换hbase-server-2.3.7.jar包,重启服务,验证ok!
总结
程序中报方法找不到的原因除了jar包冲突,不同版本代码不同外,还可能是本文遇到的这种问题。