记一次方法找不到的排查过程

1,388 阅读3分钟
问题背景

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表没有任何问题,同一张表后面再查询的时候就出现这个问题了。出于良好的职业道德,虽然测试还没有发现这个问题,我还是赶紧研究研究修复一下吧。

干活

错误日志:

12.png

从堆栈信息上看是在操作hfile的时候出错了,hudi-0.11.0版本自动开启了metadata表,这个表的存储格式是hfile,因此之前的版本可能遇不到这个问题。方法找不到,可能原因就是jar包版本冲突了呗,应该很好搞定吧,至少前期我是这么想的。

首先看下这个类org.apache.hadoop.hdfs.client.HdfsDataInputStream都出现在哪些jar包中,使用命令grep -rn ./hive/lib/搜索一下,发现没有找到。那可能是hdfs,tez等组件下有,最终是发现在tezhadoop-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,因为我们要查看的jvmhiveserver2

  • 首先监视org.apache.hudi.org.apache.hadoop.hbase.io.FsDataInputStreamWrapper.updateInputStreamStatistics方法,然后执行报错的sql触发异常。

监视命令:trace org.apache.hudi.org.apache.hadoop.hbase.io.FsDataInputStreamWrapper updateInputStreamStatistics

监视结果:

13.png

能看出来确实是被监视的方法抛出的异常,但是看不出来其他信息了。

  • 看下org.apache.hudi.org.apache.hadoop.hbase.io.FsDataInputStreamWrapperorg.apache.hadoop.hdfs.client.HdfsDataInputStream分别是从哪个jar包加载进来的

命令:sc -d org.apache.hudi.org.apache.hadoop.hbase.io.FsDataInputStreamWrapper

144.png

类加载路径没有问题,那就反编译看下代码吧,反编译命令: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包冲突,不同版本代码不同外,还可能是本文遇到的这种问题。