Impala Catalogd启动报错NoClassDefFoundError: org.apache.hadoop.fs.FileSystem

开端

今天在部署Impala时遇到一个NoClassDefFoundError的错误,本以为只是CLASSPATH的小问题,没想到调查了一天......

事情是这样的,编译Impala生成RPM,部署到另一台机器上后,statestored能正常启动,catalogd启动报错:

I0411 13:38:14.642241 113789 init.cc:599] OS distribution: CentOS Linux 8 (Core)
OS version: Linux version 5.4.243-1.el7.elrepo.x86_64 (mockbuild@Build64R7) (gcc version 9.3.1 20200408 (Red Hat 9.3.1-2) (GCC)) #1 SMP Tue May 16 10:43:45 EDT 2023
Clock: clocksource: 'tsc', clockid_t: CLOCK_MONOTONIC
I0411 13:38:14.642261 113789 init.cc:600] Process CGroup Info: memory.limit_in_bytes=9223372036854771712
I0411 13:38:14.642583 113789 init.cc:601] Process ID: 113789
I0411 13:38:14.642594 113789 init.cc:602] Default AES cipher mode for spill-to-disk: AES-GCM
I0411 13:38:14.746702 113789 init.cc:440] Using Java weigher sizeof
I0411 13:38:15.480756 113789 webserver.cc:395] Starting webserver on 0.0.0.0:25020
I0411 13:38:15.480811 113789 webserver.cc:411] Document root: /opt/impala
I0411 13:38:15.481134 113789 webserver.cc:548] Webserver started
I0411 13:38:15.609732 113789 jni-util.cc:302] java.lang.NoClassDefFoundError: org.apache.hadoop.fs.FileSystem
        at org.apache.hadoop.hive.conf.valcoersion.JavaIOTmpdirVariableCoercion.<clinit>(JavaIOTmpdirVariableCoercion.java:37)
        at org.apache.hadoop.hive.conf.SystemVariables.<clinit>(SystemVariables.java:37)
        at org.apache.hadoop.hive.conf.HiveConf$ConfVars.<init>(HiveConf.java:5736)
        at org.apache.hadoop.hive.conf.HiveConf$ConfVars.<init>(HiveConf.java:5717)
        at org.apache.hadoop.hive.conf.HiveConf$ConfVars.<clinit>(HiveConf.java:536)
        at org.apache.hadoop.hive.conf.HiveConf.<clinit>(HiveConf.java:256)
        at org.apache.impala.service.JniCatalog.<clinit>(JniCatalog.java:135)
I0411 13:38:15.609787 113789 status.cc:129] NoClassDefFoundError: org.apache.hadoop.fs.FileSystem
    @          0x10a02f4
    @          0x1c21104
    @          0x1036151
    @           0xfd433d
    @           0xf5a43e
    @           0xe5b774
    @     0x7f81d2bf66a3
    @           0xf05c2e
F0411 13:38:15.611681 113789 catalog.cc:86] NoClassDefFoundError: org.apache.hadoop.fs.FileSystem
. Impalad exiting.
Minidump with no thread info available.
Wrote minidump to /var/log/impala-minidumps/catalogd/86355b1b-4d2c-4195-61ac0d9e-68059041.dmp

catalogd.ERROR也没有更多有用的信息:

Log file created at: 2024/04/11 13:38:14
Running on machine: ccycloud-3.quanlong.root.comops.site
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
E0411 13:38:14.627285 113789 logging.cc:256] stderr will be logged to this file.
log4j:WARN No appenders could be found for logger (org.apache.hadoop.conf.Configuration).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
F0411 13:38:15.611681 113789 catalog.cc:86] NoClassDefFoundError: org.apache.hadoop.fs.FileSystem
. Impalad exiting.
*** Check failure stack trace: ***
    @          0x391054d
    @          0x3912484
    @          0x390ff2c
    @          0x39129a9
    @          0x103625b
    @           0xfd433d
    @           0xf5a43e
    @           0xe5b774
    @     0x7f81d2bf66a3
    @           0xf05c2e
Minidump with no thread info available.
Wrote minidump to /var/log/impala-minidumps/catalogd/86355b1b-4d2c-4195-61ac0d9e-68059041.dmp

报错是 "NoClassDefFoundError: org.apache.hadoop.fs.FileSystem", 报的是 NoClassDefFoundError 而不是常见的 ClassNotFoundException。一开始没在意,先排查CLASSPATH的问题。

由于之前遇到过 IMPALA-12979 的问题,CLASSPATH里没有用通配符 *,而是写明了所有目录和jar包的绝对路径,这个没问题。

FileSystem这个类是在 hadoop-common 包中提供的,在CLASSPATH中确认了有这个包。为了进一步确认它被使用了,在环境变量 JAVA_TOOL_OPTIONS 里加 -verbose:class,再次启动 catalogd,能在日志里看到 ClassLoader 加载这个类的日志:

[Loaded org.apache.hadoop.fs.FileSystem from file:/opt/impala/lib/jars/hadoop-common-3.1.1.7.2.18.0-369.jar]

这让我陷入了沉思......

转机

常用的debug方法就是在好坏两个重现之间找差异。同一台机器上还有CM部署的Impala,之前的配置就是从CM部署的配置复制过去的,所以配置内容是一样的。我把CDP parcel里的impalad可执行文件和jar包全都替换成我先前部署的版本,发现CM竟然可以启动catalogd! 这说明可执行文件和jar包没问题,可能还是CLASSPATH的问题。

于是我用CM的CLASSPATH,在前面加上 /opt/impala/conf(即我手动部署的配置目录),启动手动部署的catalogd,发现还是报一样的错。再度陷入沉思......

于是开始各种查 NoClassDefFoundError 的报错机制,发现它和 ClassNotFoundException 还是有区别的。ClassNotFoundException 是这个类在CLASSPATH里真的找不到,ClassLoader报的错。NoClassDefFoundError 可能是这个类找到了,但在加载的时候初始化 static 成员时报错,导致这个类无法被使用。网上很多只说了CLASSPATH有问题,这个回答才比较全面:https://stackoverflow.com/a/2213496/5996453

看报错堆栈,确实是在初始化static成员出的错:

I0411 13:38:15.609732 113789 jni-util.cc:302] java.lang.NoClassDefFoundError: org.apache.hadoop.fs.FileSystem
        at org.apache.hadoop.hive.conf.valcoersion.JavaIOTmpdirVariableCoercion.<clinit>(JavaIOTmpdirVariableCoercion.java:37)

对应的 JavaIOTmpdirVariableCoercion.java 代码是:

java 复制代码
 34 public class JavaIOTmpdirVariableCoercion extends VariableCoercion {
 35   private static final Logger LOG = LoggerFactory.getLogger(JavaIOTmpdirVariableCoercion.class);
 36   private static final String NAME = "system:java.io.tmpdir";
 37   private static final FileSystem LOCAL_FILE_SYSTEM = new LocalFileSystem();

至于是初始化 LOCAL_FILE_SYSTEM 时的 new LocalFileSystem() 失败了,还是初始化 FileSystem 的静态成员失败了,还不得而知。奇怪的是 catalogd 日志里并没有其它的报错,难道是 JNI 相关的代码把报错忽略了?

又过了一下FileSystem.java的代码,可能初始化失败的static成员倒是有几个:

java 复制代码
    public static final Logger LOG = LoggerFactory.getLogger(FileSystem.class);
    private static final Logger LOGGER =
      LoggerFactory.getLogger(FileSystem.class);
    /** FileSystem cache. */
    static final Cache CACHE = new Cache(new Configuration());     

但是没有日志真不好往下查,于是我又开始玩弄好坏两个重现,尽可能让它们相近。机缘巧合,发现完全用CM的CLASSPATH就没问题,但哪怕是前面加上 /opt/impala/conf 都不行!所以问题可能还是在配置文件上。

真相浮出水面

逐个对比core-site.xml、hdfs-site.xml、hive-site.xml,只有细微的差别(中途我用CM加了些DataNode节点导致的)。把它们的内容都改成CM部署的一样的内容,发现还是报错。

又一次机缘巧合,想到可能是权限的问题,于是检查了一下,发现我启动catalogd用的用户名("impala"),确实没法读我部署的配置文件!

这是我当时用的复制指令:

# cp /var/run/cloudera-scm-agent/process/46-impala-CATALOGSERVER/hadoop-conf/* /opt/impala/conf

这是源文件的权限:

# ls -l /var/run/cloudera-scm-agent/process/46-impala-CATALOGSERVER/hadoop-conf/
total 16
-rw-r----- 1 impala impala 5331 Apr 11 01:49 core-site.xml
-rw-r----- 1 impala impala 2455 Apr 11 01:49 hdfs-site.xml
-rw-r----- 1 impala impala  315 Apr 11 01:49 ssl-client.xml

这是目标文件的权限:

# ls -l /opt/impala/conf/core-site.xml
-rw-r----- 1 root root 5331 Apr 12 03:02 /opt/impala/conf/core-site.xml

因为我是用root用户复制的,文件的owner变成了root,而文件的权限在 other 部分是 0,这就导致 "impala" 用户名无法读取这个文件!

把所有配置文件都加上 other 的读权限后,问题就解决了

# chmod o+r /opt/impala/conf/*
# ls -l /opt/impala/conf/core-site.xml
-rw-r--r-- 1 root root 5331 Apr 12 03:02 /opt/impala/conf/core-site.xml

没想到这个 NoClassDefFoundError 重误的原因真不是CLASSPATH有问题,而是匹配文件的问题。

归因

还有一些问题没弄清楚:

  • 为什么没有抛读不了core-site.xml文件的报错?
  • core-site.xml读不了是怎么导致NoClassDefFoundError的?具体是FileSystem还是其它哪个类的static成员初始化出问题了?

回到本地的 impala 开发环境,我重现了问题,不光是core-site.xml读不了时会出这个问题,如果core-site.xml里有语法错误(即不是一个合法的XML文件)也会出这个问题。

有意思的是 catalogd.ERROR 居然在 NoClassDefFoundError 前有具体的报错,比如core-site.xml没权限读时:

24/04/11 20:42:52 ERROR conf.Configuration: error parsing conf core-site.xml
java.io.FileNotFoundException: /home/quanlong/workspace/Impala/fe/src/test/resources/core-site.xml (Permission denied)
        at java.io.FileInputStream.open0(Native Method)
        at java.io.FileInputStream.open(FileInputStream.java:195)
        at java.io.FileInputStream.<init>(FileInputStream.java:138)
        at java.io.FileInputStream.<init>(FileInputStream.java:93)
        at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:90)
        at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:188)
        at org.apache.hadoop.conf.Configuration.parse(Configuration.java:3023)
        at org.apache.hadoop.conf.Configuration.getStreamReader(Configuration.java:3119)
        at org.apache.hadoop.conf.Configuration.loadResource(Configuration.java:3077)
        at org.apache.hadoop.conf.Configuration.loadResources(Configuration.java:3050)
        at org.apache.hadoop.conf.Configuration.loadProps(Configuration.java:2928)
        at org.apache.hadoop.conf.Configuration.getProps(Configuration.java:2910)
        at org.apache.hadoop.conf.Configuration.get(Configuration.java:1264)
        at org.apache.hadoop.conf.Configuration.getTrimmed(Configuration.java:1318)
        at org.apache.hadoop.conf.Configuration.getInt(Configuration.java:1543)
        at org.apache.hadoop.fs.FileSystem$Cache.<init>(FileSystem.java:3495)
        at org.apache.hadoop.fs.FileSystem.<clinit>(FileSystem.java:194)
24/04/11 20:42:52 INFO util.JvmPauseMonitor: Starting JVM pause monitor
24/04/11 20:42:52 INFO conf.HiveConf: Found configuration file file:/home/quanlong/workspace/Impala/fe/src/test/resources/hive-site.xml
F0411 20:42:52.422523 16481 catalog.cc:86] NoClassDefFoundError: org.apache.hadoop.fs.FileSystem
. Impalad exiting.
*** Check failure stack trace: ***
    @          0x3982bbd  google::LogMessage::Fail()
    @          0x3984af4  google::LogMessage::SendToLog()
    @          0x398259c  google::LogMessage::Flush()  
    @          0x3985019  google::LogMessageFatal::~LogMessageFatal()
    @          0x103c3bd  impala::Catalog::Catalog()
    @           0xffa5b9  impala::CatalogServer::Start()
    @           0xf3d8d7  CatalogdMain()
    @           0xf3c25f  main
    @     0x7f7f21435c87  __libc_start_main
    @           0xf3c09a  _start
Minidump with no thread info available.
Wrote minidump to /home/quanlong/workspace/Impala/logs/cluster/minidumps/catalogd/f13bee4f-b6fc-4a6a-392f1782-32ed878f.dmp

从第一个报错堆栈能清晰地看到,初始化 FileSystem 的 static 成员 CACHE 时,需要创建一个 Configuration 对象,这一步因为读不了 core-site.xml 而出错了。从而也就产生了 "NoClassDefFoundError: org.apache.hadoop.fs.FileSystem" 的报错。这回答了第二个问题。

为什么第一个报错没在部署的远程机器上打印出来?

  • 难道是RELEASE build和DEBUG build的区别?我本地是DEBUG build,所以默认有打印C++的堆栈。重新编译成 RELEASE build试了一下,发现日志也是有这个报错的。
  • 难道是系统差异导致的?远程机器是Redhat 8.9,本地开始环境是Ubuntu 18.04。于是试了下在Redhat 8.9里搭建impala开发环境并重现问题,发现日志也是有第一个报错的。

后来突然意识到,第一个报错FileNotFoundException的日志格式和第二个报错NoClassDefFoundError的日志格式是不同的:

24/04/11 20:42:52 ERROR conf.Configuration: error parsing conf core-site.xml
java.io.FileNotFoundException: ...
F0411 20:42:52.422523 16481 catalog.cc:86] NoClassDefFoundError: org.apache.hadoop.fs.FileSystem

第二个报错的格式是glog配置的格式,而第一个的格式在impala日志中很少见,没有源码文件名和行号,也没有线程id。回看部署机器上的 catalogd.ERROR,开头其实有段 log4j 的warning:

log4j:WARN No appenders could be found for logger (org.apache.hadoop.conf.Configuration).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
F0411 13:38:15.611681 113789 catalog.cc:86] NoClassDefFoundError: org.apache.hadoop.fs.FileSystem

这应该是第一个报错想打印却打印不出来的warning!然后我发现,开发环境的配置文件里是有 log4j.properties 的,但部署环境的配置文件里没有。原来这就是真正的报错没打印的原因!在部署的远程机器里补上 log4j.properties 后,确实就能看到第一个报错的日志了。提了 IMPALA-12999 修复这个问题。

Impala的日志打印很特别,因为代码里即有C++又有Java的。C++代码使用glog打印日志,Java代码使用log4j打印日志,然后通过Impala实现的GlogAppender把 log4j 的日志导到 glog 里去打印。而 GlogAppender 的初始化是在 JniCatalog 的构造函数里的,这个发生在初始化 static 成员之后。因此第一个报错抛出来时,log4j还没被重定向到 glog 里,只是打印到了 stderr,从而出现在了 catalogd.ERROR(但不在catalogd.INFO)里。关于这部分代码的实现,之前梳理过一篇文章:Impala的log4j和glog配置

总结

许多事情回过头看都很简单,不过是 "当局者迷,旁观者清"。作为 "当局者",一定要发散思维,不要在一条路上死磕,比如坚信 NoClassDefFoundError 只能是 CLASSPATH 有问题导致的,那还要浪费很多时间。

知识点:

  • Impala部署时也需要log4j.properties,以正常打印初始化时的Java日志。等log4j被重定向到glog后,这个配置就没用了。
  • ClassNotFoundException 是 ClassLoader 无法在 CLASSPATH 中找到某个类,经常是编译时和运行时的CLASSPATH不一致导致的。
  • NoClassDefFoundError 可能由 ClassNotFoundException 导致,也可能由其它原因如 static 成员初始化失败导致。
相关推荐
【D'accumulation】26 分钟前
典型的MVC设计模式:使用JSP和JavaBean相结合的方式来动态生成网页内容典型的MVC设计模式
java·设计模式·mvc
试行41 分钟前
Android实现自定义下拉列表绑定数据
android·java
茜茜西西CeCe1 小时前
移动技术开发:简单计算器界面
java·gitee·安卓·android-studio·移动技术开发·原生安卓开发
救救孩子把1 小时前
Java基础之IO流
java·开发语言
小菜yh1 小时前
关于Redis
java·数据库·spring boot·redis·spring·缓存
宇卿.1 小时前
Java键盘输入语句
java·开发语言
浅念同学1 小时前
算法.图论-并查集上
java·算法·图论
立志成为coding大牛的菜鸟.1 小时前
力扣1143-最长公共子序列(Java详细题解)
java·算法·leetcode
鱼跃鹰飞1 小时前
Leetcode面试经典150题-130.被围绕的区域
java·算法·leetcode·面试·职场和发展·深度优先
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring