FlinkSql on YARN 提交避坑指南:从踩坑到稳定运行

发布时间:2026/10/15 21:59:01
FlinkSql on YARN 提交避坑指南:从踩坑到稳定运行
1. 为什么一个提交命令能折腾我一整天第一次把 FlinkSql 任务往 YARN 上提交的时候我以为这活儿半小时就能收工。结果从上午十点一直折腾到凌晨一点中间经历了任务提交后立刻消失、日志里全是看不懂的类加载报错、资源申请卡在 ACCEPTED 状态不动、SQL 里的自定义函数在本地跑得好好的上集群就找不到……这些坑一个比一个隐蔽官方文档里要么一笔带过要么默认你已经懂了。这篇记录就是把我踩过的这些坑一个个摊开来讲。核心关键词是FlinkSql on yarn 提交涉及到的场景包括用 Flink SQL 写好的作业怎么通过命令行提交到 YARN 集群、提交过程中常见的配置陷阱、资源申请阶段的排查思路、以及 SQL 作业特有的依赖管理问题。适合谁看如果你已经能跑通 Flink 的 WordCount 示例但对把 SQL 作业稳定提交到 YARN 这件事心里没底那这篇就是写给你的。如果你还在本地 IDE 里跑 Flink那建议先补一下 Flink on YARN 的基础概念再回来。我尽量不堆术语每个配置项都告诉你它到底在管什么、不配会怎样、配错了会报什么错。文章里涉及的所有环境信息我都做了脱敏处理你按自己的集群情况替换就行。2. 提交前必须搞清楚的几个核心概念2.1 三种提交模式到底选哪个Flink 提交到 YARN 有三种模式Session 模式、Per-Job 模式、Application 模式。很多人上来就随便选一个结果后面遇到一堆问题才发现模式选错了。Session 模式是先在 YARN 上起一个常驻的 Flink 集群然后所有作业都往这个集群里提交。好处是资源复用、提交快坏处是作业之间互相影响一个作业把 TaskManager 的内存打爆了整个 Session 里的其他作业全跟着遭殃。我一开始图省事用的就是 Session 模式结果一个 SQL 作业的 JOIN 数据量太大直接把整个 Session 搞挂了连带其他几个正常作业一起下线。Per-Job 模式是每个作业单独起一个 YARN 应用作业之间完全隔离。缺点是每个作业都要重新申请资源、启动 JobManager提交延迟高。Application 模式是 Per-Job 的进化版main 方法在集群里执行客户端只负责提交适合生产环境。如果你只是做测试或者作业数量少、资源充足Per-Job 模式最省心。生产环境建议直接上 Application 模式别在 Session 模式上浪费时间。2.2 YARN 队列和资源池的关系提交命令里有个-Dyarn.application.queue参数很多人随手填一个default就完事了。但实际生产集群里队列往往和资源池绑定不同队列的可用资源、优先级、甚至 ACL 权限都不一样。我遇到过一次提交后一直卡在 ACCEPTED 状态查了半天发现是目标队列的资源已经被占满了而 YARN 的调度策略是 FIFO我的作业排在后面一直等不到资源。后来换成另一个空闲队列秒过。排查队列资源的方法很简单用yarn queue -status 队列名就能看到当前队列的已用资源和容量。提交前先看一眼能省掉很多无谓的等待。2.3 FlinkSql 作业的特殊性普通 Flink 作业的依赖都在 Jar 包里提交时带上-c指定主类就行。但 FlinkSql 作业不一样它的逻辑全在 SQL 语句里可能还涉及自定义 UDF、连接器 Jar 包、配置文件等。这些依赖怎么带到集群上是 FlinkSql on yarn 提交最容易出问题的地方。我见过最常见的错误就是本地 IDE 里跑得好好的 SQL提交到 YARN 后报ClassNotFoundException原因是 UDF 的 Jar 包没有被打进作业 Jar 里或者没有通过-C参数指定。3. 提交命令逐参数拆解与实操配置3.1 基础提交命令模板先给一个我实测可用的提交命令模板基于 Flink 1.13 到 1.17 都验证过./bin/flink run-application \ -t yarn-application \ -Djobmanager.memory.process.size2048m \ -Dtaskmanager.memory.process.size4096m \ -Dtaskmanager.numberOfTaskSlots2 \ -Dyarn.application.queueyour_queue_name \ -Dyarn.application.namemy_flink_sql_job \ -Dparallelism.default4 \ -Dyarn.ship-files/path/to/udf.jar,/path/to/connector.jar \ -c org.apache.flink.table.client.SqlClient \ /path/to/flink-sql-job.jar \ --update-statement INSERT INTO sink_table SELECT ... FROM source_table这个命令看起来长但每个参数都有它存在的理由。下面逐个拆。3.2 内存参数怎么算才不浪费jobmanager.memory.process.size和taskmanager.memory.process.size这两个参数是最容易拍脑袋填的。填小了作业跑不起来填大了浪费集群资源。JobManager 的内存主要消耗在作业调度、Checkpoint 协调、SQL 解析上。对于 FlinkSql 作业如果 SQL 特别复杂比如几十个 JOINJobManager 的内存需求会明显上升。我的经验值是简单 SQL 给 1024m 到 2048m复杂 SQL 给 2048m 到 4096m。TaskManager 的内存计算稍微复杂一点。Flink 1.10 之后引入了细粒度的内存模型taskmanager.memory.process.size是总内存里面还要分给框架堆内存、任务堆内存、网络缓冲、托管内存等。对于 SQL 作业托管内存Managed Memory特别重要因为排序、哈希 JOIN、聚合操作都要用它。一个粗略的估算公式托管内存 ≈ taskmanager.memory.process.size × 0.4如果 SQL 里有大表 JOIN 或者 GROUP BY托管内存不够会直接报OutOfMemoryError或者频繁 Spill 到磁盘导致性能骤降。我一般会把taskmanager.memory.managed.fraction显式设置为 0.5给托管内存留足空间。3.3 并行度设置与 Slot 数量的配合parallelism.default和taskmanager.numberOfTaskSlots这两个参数要配合着看。假设你设了并行度 4每个 TaskManager 有 2 个 Slot那至少需要 2 个 TaskManager 才能跑起来。但这里有个坑YARN 申请 TaskManager 是逐个申请的如果集群资源紧张可能只申请到 1 个 TaskManager作业就会一直卡在创建阶段。我遇到过并行度设了 8结果集群只能给 3 个 TaskManager作业跑了半小时还在等资源。提交前先用yarn queue -status确认队列有足够资源或者把并行度调低到集群能承受的范围。3.4 依赖管理的三种方式FlinkSql 作业的依赖管理是最容易翻车的地方。我总结下来有三种方式第一种是把所有依赖打进作业 Jar 里用 Maven Shade 插件做 Fat Jar。这种方式最省心但 Jar 包会很大而且如果依赖之间有冲突Shade 的时候要小心处理。第二种是用-C参数在提交时指定额外的 Jar 包。这种方式灵活但每次提交都要带一堆参数容易漏。第三种是用yarn.ship-files把依赖文件上传到集群。这种方式适合依赖文件多、不想打进 Jar 的情况。我一般推荐第一种把 UDF 和连接器都打进 Fat Jar提交命令干净不容易出错。如果依赖实在太多再用第二种补充。4. 完整提交过程与现场记录4.1 从打包到提交的完整流程假设你已经写好了一个 FlinkSql 作业现在要把它提交到 YARN。完整流程如下第一步用 Maven 打包。确保pom.xml里引入了flink-table-planner-blink、flink-connector-kafka等必要依赖并且用 Shade 插件打成 Fat Jar。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.2.4/version executions execution phasepackage/phase goalsgoalshade/goal/goals configuration transformers transformer implementationorg.apache.maven.plugins.shade.resource.ServicesResourceTransformer/ /transformers /configuration /execution /executions /plugin第二步把打好的 Jar 上传到提交机器。如果提交机器就是集群节点直接放本地路径就行。第三步执行提交命令。提交后终端会输出一个 YARN Application ID记下这个 ID后面查日志要用。第四步用yarn application -status ApplicationID查看作业状态。如果状态是 RUNNING说明提交成功。如果是 ACCEPTED说明还在等资源。如果是 FAILED就要去查日志了。4.2 日志查看的层级和技巧Flink on YARN 的日志分好几层很多人不知道去哪找。我按排查顺序列一下第一层是客户端日志就在提交命令的终端输出里。如果提交命令本身报错问题一般在这里。第二层是 YARN 应用日志用yarn logs -applicationId ApplicationID查看。这里面包含 JobManager 和 TaskManager 的日志。第三层是 Flink Web UI默认在 JobManager 所在节点的 8081 端口。如果作业已经 RUNNING通过 Web UI 看反压、Checkpoint、异常堆栈最直观。我排查问题时一般先看客户端日志再看 YARN 应用日志里的 JobManager 日志最后看 TaskManager 日志。大部分问题在前两层就能定位。4.3 一个真实的提交失败案例有一次提交一个带 UDF 的 SQL 作业客户端显示提交成功但 YARN 应用状态一直是 ACCEPTED等了十分钟也没变成 RUNNING。查 JobManager 日志发现java.lang.NoClassDefFoundError: com/example/MyUDF原因是 UDF 的 Jar 包没有打进 Fat Jar也没有通过-C指定。本地 IDE 里能跑是因为 IDE 的 classpath 包含了 UDF 的编译输出目录但打包时没有把它包含进去。解决方法是在pom.xml里把 UDF 模块作为依赖引入或者用-C参数指定 UDF Jar 的路径。我选择了前者因为这样提交命令更干净。5. 常见问题速查与排查技巧5.1 提交后作业立刻消失这是最常见的问题之一。现象是提交命令返回成功但 YARN 应用列表里找不到这个应用或者状态直接变成 FINISHED。原因通常是作业的 main 方法执行完就退出了没有触发execute()或者executeSql()的阻塞等待。FlinkSql 作业如果用TableEnvironment.executeSql()执行 INSERT 语句需要确保最后调用了env.execute()或者tEnv.execute()来触发作业提交。另一个原因是 SQL 语句本身有问题比如源表或目标表不存在导致作业初始化失败后直接退出。这种情况要看 JobManager 日志里的异常堆栈。5.2 资源申请一直卡在 ACCEPTED前面提过这通常是队列资源不足导致的。排查步骤用yarn queue -status 队列名查看队列资源使用情况。用yarn application -list查看当前队列里有哪些应用在跑。如果队列资源确实满了要么等要么换队列要么降低作业的资源需求。还有一种可能是 YARN 的调度器配置了最大应用数限制队列里应用数量达到上限后新应用就会一直排队。这种情况需要联系集群管理员调整配置。5.3 ClassNotFoundException 和 NoSuchMethodError这两个错误是依赖问题的典型表现。ClassNotFoundException说明类根本没找到通常是 Jar 包没带上。NoSuchMethodError说明类找到了但方法签名不匹配通常是依赖版本冲突。排查方法用jar tf your-job.jar | grep 类名确认类是否在 Jar 里。用mvn dependency:tree查看依赖树找出冲突的版本。在 Shade 插件里配置relocation把冲突的包重命名。我遇到过一次NoSuchMethodError查了半天发现是 Flink 集群自带的某个依赖版本和作业 Jar 里的版本不一致。解决方法是在 Shade 插件里把作业依赖的版本 relocate 到自定义包名下避免和集群自带的冲突。5.4 Checkpoint 失败导致作业重启FlinkSql 作业如果配置了 CheckpointCheckpoint 失败会导致作业重启。常见的 Checkpoint 失败原因包括状态后端配置错误比如用了 HDFS 但没配 HA。Checkpoint 超时通常是因为状态太大或者网络慢。托管内存不足导致异步快照失败。我一般会把 Checkpoint 超时时间设长一点比如 10 分钟并且开启非对齐 Checkpoint 来减少反压影响。5.5 SQL 里的时间属性和 Watermark 不生效FlinkSql 里定义事件时间属性和 Watermark 是有固定语法的写错了不会报错但也不会生效。常见错误是把WATERMARK FOR ts AS ts - INTERVAL 5 SECOND写成了WATERMARK FOR ts AS ts - 5后者在部分版本里会被解析成非法表达式。另外如果源表的时间字段是字符串类型需要先用TO_TIMESTAMP转换否则 Watermark 生成会失败。6. 几个让我少走弯路的实操心得6.1 提交前先在本地用 MiniCluster 验证Flink 提供了MiniCluster可以在本地模拟一个完整的 Flink 集群包括 JobManager 和 TaskManager。提交到 YARN 之前先在本地用 MiniCluster 跑一遍能提前发现大部分 SQL 语法错误和依赖问题。Configuration conf new Configuration(); conf.setString(taskmanager.numberOfTaskSlots, 2); MiniClusterWithClientResource cluster new MiniClusterWithClientResource( new MiniClusterResourceConfiguration.Builder() .setConfiguration(conf) .setNumberTaskManagers(1) .build()); cluster.before(); // 在这里执行你的 SQL 作业 cluster.after();6.2 用 savepoint 做版本升级的兜底FlinkSql 作业升级时如果状态不兼容作业会启动失败。我一般会在升级前先触发一次 savepoint万一新版本有问题可以回滚到旧版本并从 savepoint 恢复。触发 savepoint 的命令./bin/flink savepoint JobID SavepointPath -yid ApplicationID从 savepoint 恢复./bin/flink run-application -t yarn-application \ -s SavepointPath \ ...6.3 日志级别调成 DEBUG 能省很多时间默认的 INFO 级别日志会漏掉很多关键信息。排查问题时临时把 JobManager 和 TaskManager 的日志级别调成 DEBUG能看到更详细的类加载、资源申请、SQL 解析过程。在log4j.properties里改log4j.logger.org.apache.flinkDEBUG log4j.logger.org.apache.hadoopDEBUG记得问题解决后调回 INFO否则日志量会大到磁盘扛不住。6.4 提交命令写成脚本别手敲提交命令参数多手敲容易漏。我一般会写一个submit.sh脚本把参数都放在变量里提交时只改几个关键变量就行。这样既不容易出错也方便版本管理。#!/bin/bash JOB_NAMEmy_sql_job QUEUEroot.default PARALLELISM4 JM_MEM2048m TM_MEM4096m JAR_PATH/opt/flink/jobs/my-job.jar ./bin/flink run-application \ -t yarn-application \ -Djobmanager.memory.process.size$JM_MEM \ -Dtaskmanager.memory.process.size$TM_MEM \ -Dyarn.application.queue$QUEUE \ -Dyarn.application.name$JOB_NAME \ -Dparallelism.default$PARALLELISM \ -c com.example.MySqlJob \ $JAR_PATH6.5 关注 YARN 的 NodeManager 日志有时候问题不在 Flink 侧而在 YARN 侧。比如 TaskManager 容器启动失败Flink 日志里只看到容器退出具体原因要去 NodeManager 日志里找。NodeManager 日志一般在/var/log/hadoop-yarn/目录下按容器 ID 搜索就能找到对应的日志文件。我遇到过一次 TaskManager 启动后立刻退出Flink 日志里只有一行Container exited with a non-zero exit code。去 NodeManager 日志里一查发现是容器内存超限被 YARN 杀掉了。原因是taskmanager.memory.process.size设得比 YARN 容器内存上限还大YARN 直接 OOM Killer 把容器杀了。这个坑的教训是taskmanager.memory.process.size必须小于等于 YARN 容器内存上限否则容器会被 YARN 强制杀掉。YARN 容器内存上限由yarn.nodemanager.resource.memory-mb和队列的maximum-am-resource-percent等参数共同决定提交前最好确认一下。7. 关于 SQL 作业状态恢复的补充FlinkSql 作业的状态恢复比普通 DataStream 作业要复杂一些因为 SQL 作业的状态是算子内部管理的用户看不到具体的状态结构。如果 SQL 语句改了比如加了一个字段或者改了一个 JOIN 条件状态可能就不兼容了。我的做法是每次修改 SQL 后先在测试环境用 savepoint 恢复一次确认状态兼容再上生产。如果不兼容要么改 SQL 保持状态结构不变要么放弃状态从零开始跑。另外FlinkSql 作业的 Checkpoint 配置也很关键。我一般会设置execution.checkpointing.interval60000 execution.checkpointing.timeout600000 execution.checkpointing.min-pause30000 execution.checkpointing.max-concurrent-checkpoints1 state.backendrocksdb state.backend.incrementaltrueRocksDB 状态后端配合增量 Checkpoint能显著减少大状态作业的 Checkpoint 开销。但 RocksDB 会占用更多本地磁盘需要确保 TaskManager 所在节点有足够的磁盘空间。8. 最后再分享几个小技巧提交 FlinkSql 作业到 YARN 这件事说难不难说简单也不简单。核心就三件事依赖带全、资源算对、日志会看。依赖带全靠 Fat Jar 和-C参数资源算对靠理解内存模型和队列容量日志会看靠知道去哪找、怎么看。如果你刚开始接触 FlinkSql on yarn建议先用一个最简单的 SQL 作业跑通全流程然后再逐步加 UDF、加连接器、加 Checkpoint。每加一个东西就提交一次确认没问题再加下一个。这样出问题时容易定位不会一下子面对一堆报错不知道从哪下手。还有一个我踩过的坑不同 Flink 版本的提交命令参数名可能不一样。比如 Flink 1.10 之前用的是-yjm和-ytm1.11 之后改成了-Djobmanager.memory.process.size和-Dtaskmanager.memory.process.size。提交前先确认一下自己用的 Flink 版本别照着旧文档敲命令。最后YARN 的资源是共享的提交作业前先看看队列里有没有其他大作业在跑。我曾经在一个队列里同时提交了两个大 SQL 作业结果两个互相抢资源都跑得特别慢。后来错开时间提交每个作业都能拿到足够资源整体完成时间反而更短。