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 成员初始化失败导致。
相关推荐
天天摸鱼的java工程师1 分钟前
从被测试小姐姐追着怼到运维小哥点赞:我在项目管理系统的 MySQL 优化实战
java·后端·mysql
点赋科技11 分钟前
沙市区举办资本市场赋能培训会 点赋科技分享智能消费新实践
大数据·人工智能
周某某~12 分钟前
四.抽象工厂模式
java·设计模式·抽象工厂模式
YSGZJJ32 分钟前
股指期货技术分析与短线操作方法介绍
大数据·人工智能
Doker 多克37 分钟前
Flink CDC —部署模式
大数据·flink
异常君40 分钟前
高并发数据写入场景下 MySQL 的性能瓶颈与替代方案
java·mysql·性能优化
Guheyunyi41 分钟前
监测预警系统重塑隧道安全新范式
大数据·运维·人工智能·科技·安全
烙印60143 分钟前
MyBatis原理剖析(二)
java·数据库·mybatis
你是狒狒吗1 小时前
TM中,return new TransactionManagerImpl(raf, fc);为什么返回是new了一个新的实例
java·开发语言·数据库
勤奋的知更鸟1 小时前
Java编程之组合模式
java·开发语言·设计模式·组合模式