适用问题:ClassNotFoundException、NoClassDefFoundError、NoSuchMethodError、ClassCastException、LinkageError、不同环境(本地/集群)表现不一致、在 TaskManager 上失败等。
一、先理解 Flink on YARN 的"包从哪来"
Flink 作业运行时可能同时存在多套依赖来源:
- Flink 发行包自带 lib
位于 Flink 集群分发目录的 lib/(YARN 模式下会被分发到容器里)。
- yarn.provided.lib.dirs(父加载器可见的额外 lib)
通常用于放"大依赖/多作业共用依赖",作用类似集群级共享库。
- 作业 Jar(用户代码 Jar)
通过 flink run 提交的 jar;如果是 fat/uber jar,里面还携带第三方依赖。
- 插件(plugins/)
一般用于 connector/format 的插件化加载(不同版本隔离)。
核心点:Flink 默认 child-first(优先作业 jar),个别包会被强制 parent-first(Flink 自己的、日
志、部分 Java/Scala 等),并可通过 classloader.parent-first-patterns.additional 调整。
-D classloader.resolve-order=child-first(默认是child-first,不建议改为 parent-first)
-D classloader.parent-first-patterns.additional = okhttp3.,okio.,kotlin.
二、 先按异常类型分流(最快定位方向)
A. ClassNotFoundException / NoClassDefFoundError
含义:运行时找不到类。优先怀疑:
- 依赖没带上(作业 jar 非 fat、provided 目录缺包)
- 依赖拆分成多个 jar(例如 Kotlin/OkHttp/Okio),你只放了其中一部分
- 提交命令参数(-C/-yD)或部署目录不一致,导致容器里没有对应 jar
结论倾向:缺依赖/类不可见。
B. NoSuchMethodError / IncompatibleClassChangeError
含义:类找到了,但版本/二进制签名不匹配。优先怀疑:
- 同一个库出现两份不同版本(作业 jar + provided / Flink lib)
- 依赖传递导致版本漂移(A 依赖 okhttp 4.9.3,B 依赖 okhttp 3.x)
- shaded 与非 shaded 混用
结论倾向:版本冲突。
C. ClassCastException(尤其报"cannot be cast to ..."且类名相同)
含义:同名类被不同 ClassLoader 加载,两份"同类"互相不能 cast。优先怀疑:
- 同一个 jar 在 parent(provided/lib)和 child(作业 jar)各一份 或 connector/plugin 与作业 jar 重复
结论倾向:类加载器隔离导致的重复类。
三、现场信息抓取(YARN 上先拿到"容器里到底有什么")
3.1 找到失败的容器日志
yarn logs -applicationId <appId> > app.log
或只抓某个 container:
yarn logs -applicationId <appId> -containerId <containerId>
重点看:
TaskManager 的 stderr/stdout(多数类问题发生在 TM)报错栈第一处 Caused by(最关键)
3.2 确认容器里加载了哪些 jar
在日志中搜索:
Classpath: / class path(不同版本可能打印位置不同)
Found jar / Adding jar / Using configuration 等关键词
如果日志不够,进入"增强日志"步骤(见第 五 节)。
四、快速自检清单(90% 的问题在这里)
4.1 你的依赖到底在哪里?
- 作业 jar 是否是 fat jar?检查 jar 里是否有 BOOT-INF/lib(Spring Boot)或大量第三方包(jar tf xxx.jar | head)。
- yarn.provided.lib.dirs 是否真正生效?配置是否在提交使用的 flink-conf.yaml 生效?是否指向 HDFS/本地且可被 YARN 访问?目录是否有读权限?
- plugins/ 是否放了 connector(kafka、hudi、iceberg)?是否与作业 jar 重复?
4.2 依赖是否"成组"出现(常见坑)
很多库不是单 jar 就能跑,典型例子:
OkHttp 4.x:需要 Okio、Kotlin stdlib(且可能需要 jdk7/jdk8 扩展包,视版本而定)
Jackson:core / databind / annotations 版本需对齐
Netty:一套版本需对齐(Flink 自带,用户不要随便覆盖)
经验:如果用 provided 方式提供依赖,请按依赖树把传递依赖一起放齐,并保持版本一致。
五、开启类加载调试日志(定位"到底从哪个 jar 加载的")
在提交或配置中增加(不同日志框架版本可能是 log4j/log4j2,原则相同):
-Dlog4j.logger.org.apache.flink.runtime.classloading=DEBUG
你会在日志里看到类似:
- 某某 ClassLoader 加载了 com.xxx.Foo,来源 jar 路径
- 哪些 jar 被加入用户类路径
这一步可以直接回答:
- 类究竟来自"作业 jar"还是 "provided/lib"
- 是否出现一份类被加载了两次(两个不同 ClassLoader)
六、用"依赖树"确认应该有什么(本地就能做)
Maven
mvn -q dependency:tree > dep.tree.txt
针对某个库过滤:
mvn -q dependency:tree | grep -E "okhttp|okio|kotlin|jackson|netty"
Gradle
./gradlew dependencies --configuration runtimeClasspath > deps.txt
你要确认两件事:
- 运行时依赖是否齐全(ClassNotFoundException常用,简称 CNF)
- 同一 group/artifact 是否出现多个版本(冲突常用)
七、处理策略:缺依赖 vs 冲突(给出可选解)
7.1 缺依赖(CNF / NoClassDefFoundError)
优先推荐顺序:
- 直接打 fat jar(最稳),用 shade/assembly 把运行时依赖都打进作业 jar(排除 Flink 自带依赖,如 flink-*, scala-*, slf4j/log4j 等)。
- 或补齐 provided 目录依赖(成组补齐),不要只放一个 okhttp.jar,要把其传递依赖也放进去。
- 校验 yarn.provided.lib.dirs 配置是否对当前 job 生效(常见是改了配置但提交用的是另一个 conf)。
7.2 版本冲突(NoSuchMethodError / IncompatibleClassChangeError)
可选处理:
- 消除重复来源:不要同时在作业 jar 和 provided/flink lib 放同一依赖
- 锁版本:用 dependencyManagement / Gradle constraints 固定版本
- 必要时用 shading relocation:把你自己的三方库 relocate 到私有命名空间(例如 com.myjob.shaded.okhttp3...),避免和集群侧冲突
- 很少数场景:用 classloader.parent-first-patterns.additional 强制 parent-first(谨慎使用,容易引入另一类冲突)
7.3 类加载器重复导致 ClassCastException
典型解决:
- 确保同一个库只在一个"层级"出现:要么只在作业 jar,要么只在 provided/plugin
- connector/plugin 与作业 jar 里不要重复放同款依赖
- 必要时对重复库做 shading relocation
八、Flink on YARN 的常见"陷阱案例"
- 只在本地 IDE 跑通,YARN 上 CNF。原因:本地 classpath 完整,YARN 容器缺 jar;或 provided 目录没生效/路径权限问题。
- JM 正常,TM 报缺类。原因:用户 jar 分发到 TM 失败、或 TM 侧 classpath 不同、或动态加载的算子在 TM 初始化时报错。
- 升级作业依赖后出现 NoSuchMethodError。原因:集群 lib/provided 中仍然是旧版本,child/parent 加载顺序导致混用。
- OkHttp/Kotlin 相关。OkHttp 4.x 引入 Kotlin 生态依赖,provided 只放 okhttp 经常缺类;需要按依赖树补齐(kotlin-stdlib、okio、annotations 等),并注意版本对齐。
九、建议的落地规范(减少以后踩坑)
- 明确约定:集群 lib/provided 放什么、作业 jar 放什么(二选一,尽量别混)
- 作业构建时输出:
- dependency:tree(留档)
- 最终 jar 清单(jar tf 或构建报告)
- 对"高频冲突库"建立黑名单/白名单:netty、jackson、kafka client、guava、protobuf、kotlin 等
- 集群升级/依赖变更时同步更新 provided,并做回归测试