Flink on Yarn深度解析:运行模式、提交流程与资源管理实战

FlinkYarn资源管理
于 2026-10-01 09:01:17 修改
·本内容遵循CC 4.0 BY-SA版权协议

1. 为什么Flink要跑在Yarn上:资源管理与弹性伸缩的价值

先从一个常见场景说起。你本地跑Flink作业的时候,通常就是flink run一条命令,本地起一个JobManager进程,TaskManager也在同一台机器上,跑完就完事了。这个阶段不会觉得资源管理有什么问题,因为集群规模就那么大,几个任务挤一挤总能跑。

但当你进入生产环境,面对的是几十上百个作业、多支团队共用一套大数据集群时,问题就来了。Flink作业需要多少资源?高峰期怎么扩容?低峰期资源怎么释放?多个作业之间如何隔离?如果一个作业内存泄漏,是只拖垮自己还是把别人也带崩?这些问题在没有统一资源管理器的情况下,几乎无解。你只能靠人工干预——提前预估资源、手动分配机器、半夜起来给集群扩容。这显然不是可持续发展的路。

Yarn解决的就是这个问题。它的核心职责是两件事:队列管理与资源调度。你给Yarn提交一个Application,它会按照你指定的CPU、内存需求,从某个队列里分配出一批容器(Container),在这些容器里拉起你的进程。进程退出了,容器回收,资源自动还给队列。这种模式天然适合Flink这种分布式计算框架——因为Flink的JobManager和TaskManager本质上就是需要被统一管理和调度的进程。

对我来说,Flink on Yarn最有价值的地方在于它把"资源生命周期"和"作业生命周期"绑定得很干净。你不需要预先规划一个固定大小的Flink集群,也不需要每次用完手动释放。提交作业时告诉Yarn"我要多少JobManager内存、多少TaskManager、每个TaskManager多少Slot",剩下的全部由Yarn调度完成。作业跑完,容器销毁,资源归位。这比你自己维护一套常驻的Flink集群要省心得多,尤其是在作业数量多、资源波动大的场景下,收益极其明显。

还有一点容易被忽略:Flink on Yarn等于直接把Flink的容错能力接到了Yarn的资源调度体系上。TaskManager挂了,Flink的ResourceManager会向Yarn重新申请容器,不需要人工介入;JobManager挂了,Yarn会感知到ApplicationMaster进程退出,整个作业状态一目了然,监控和告警都能在Yarn这一层统一完成。这种机制让运维复杂度降低了很多,也是很多公司坚持用Yarn而不是把Flink裸跑在物理机上的原因。

2. 三种运行模式:Session、Per-Job与Application的适用边界

Flink on Yarn最让人容易懵的地方,就是同一个部署方式居然还拆出三种模式。网上教程经常只讲其中一个,或者把三种名字混着说,导致你照着A文章的配置去跑B模式,折腾半天跑不通。

2.1 Session模式——一个常驻集群,多作业共享

Session模式是最早出现的模式,理解起来也最简单。它的做法是:先通过yarn-session.sh向Yarn申请一个Flink集群,这个集群会常驻运行,然后你通过flink run把作业提交到这个已存在的集群上执行。

这个模式的特点在"共享"二字。多个作业共享同一个JobManager,共享一批TaskManager的Slot。集群的生命周期由yarn-session.sh启动的进程管理,跟具体某个作业没有关系。作业跑完了,集群还在,接着可以提交下一个作业。

Session模式最大的问题在于资源隔离太弱。想象一下,一个TaskManager里有8个Slot,作业A占2个,作业B占4个,作业C占2个。如果作业B发生内存泄漏,把整个TaskManager进程拖垮,那么A和C的Task也会一起挂掉。这就是典型的"一颗老鼠屎坏了一锅粥"。另外,多个作业共享JobManager,JobManager本身就容易成为瓶颈——作业间的调度、Checkpoint协调、Web UI的访问,全部挤在一个进程里。

所以Session模式实际生产中用的场景在收窄。我自己的经验是,它适合两类情况:一是任务短小且数量多,比如一堆几秒钟就跑完的即席查询;二是开发和测试环境,你不想每次改个SQL都重新申请一个集群,常驻一个Session集群随提随跑,效率高很多。但对生产环境的长时间大作业,Session模式的风险还是偏高。

2.2 Per-Job模式——一作业一专属集群的经典方案

Per-Job模式解决的问题正是Session模式的隔离性弱点。每提交一个作业,Yarn都会为这个作业单独创建一个Flink集群。 这个集群只有一个JobManager、若干个TaskManager,全部只服务这一个作业。作业结束,集群随之销毁。

在这种模式下,flink run命令直接通过Yarn去申请资源,而不需要预先有一个Session集群存在。客户端会把用户代码(main方法)在本地执行,生成JobGraph之后提交给Yarn上启动的JobManager。这个过程在Flink 1.15之前是生产环境的标配。

但Per-Job模式有一个让很多人忽略的隐患:你的main方法是在客户端本地跑的。也就是说,用户Jar里依赖的版本、环境变量、操作系统行为,都以客户端机器为准。如果客户端和集群端的依赖不一致,可能出现"本地能跑、上集群就炸"的尴尬。另一个问题是,如果你的main方法逻辑很重,比如里面做大量数据初始化、从外部系统拉一堆配置,客户端的网络和内存也得跟着承担压力。

Flink从1.15版本开始,把Per-Job模式标记为废弃。原因也简单:Application模式完全可以替代它,而且解决了main方法在客户端执行的痛点。如果你还在用旧版本的Per-Job,建议有计划地迁移。

2.3 Application模式——客户端不再承担main()执行职责

Application模式是Flink 1.11引入的。它跟Per-Job一样,每个作业独占一个集群,但关键区别在于main方法在集群端的ApplicationMaster进程里执行,而不是在客户端。

这个细节的差别有多大?我举个例子。你的作业代码里有一行env.setStateBackend(new RocksDBStateBackend("hdfs:///checkpoint")),在Per-Job模式下,这行代码在客户端本地执行,客户端就得能访问HDFS。在Application模式下,这行代码在集群内部执行,跟客户端无关,客户端唯一的任务就是上传Jar和触发提交。

这意味着你可以在任意一台机器上执行提交命令,甚至是在你的笔记本电脑上(只要能连通Yarn的ResourceManager和HDFS),不用担心本地环境跟集群环境的差异。而且JobGraph在集群内直接生成,减少了客户端到JobManager之间传输JobGraph的序列化环节,大作业的提交速度会有明显改善。

Application模式目前是Flink官方主推的部署方式,生产环境新作业建议无脑选它。

2.4 三种模式对比与选型建议

维度 Session模式 Per-Job模式(旧) Application模式(推荐)
集群生命周期 常驻,独立管理 随作业起停 随作业起停
main()执行位置 客户端 客户端 集群ApplicationMaster
资源隔离性 弱,多作业共享 强,作业独占 强,作业独占
启动速度 快,无需等集群启动 慢,需等应用启动 中等,需等应用启动
运维复杂度 需额外管理Session集群 低 低
适用场景 测试环境、小任务 旧版本生产环境 生产环境首选

选型判断其实不需要纠结太久。生产环境直接上Application模式,开发测试图省事就Session模式。Per-Job模式理解为过渡期的产物就行,不需要专门学了。

3. 作业提交流程全链路拆解:从命令到容器

知道三种模式的区别只是第一步,真正要弄明白Flink on Yarn,需要把"一条flink命令执行后到底发生了什么"彻底搞清楚。

3.1 提交前的准备:环境变量与配置解析

无论哪种模式,提交动作都是从一个flink脚本开始的。这个脚本会做三件事:加载环境变量、解析配置文件(flink-conf.yaml)、定位JVM参数。

这一步有个细节容易被忽略:客户端机器上最好设置了HADOOP_CLASSPATH或者HADOOP_CONF_DIR,否则Flink无法跟Yarn完成RPC通信。常见的问题是"为什么我本地能提交,但Yarn上一直ACCEPTED却不RUNNING"——很可能是客户端的Hadoop配置路径不对,Flink压根找不到你的Yarn ResourceManager地址。这种情况在Yarn集群启用了HA的时候尤其隐蔽,因为yarn-site.xml里配置了多个ResourceManager地址,你少配一个都可能出问题。

另外,flink-conf.yaml里的配置会被拆分使用。一部分配置是客户端用来构建ClusterSpecification的,比如jobmanager.memory.process.size、taskmanager.memory.process.size;另一部分会被写入最终提交给Yarn的容器启动脚本里。这意味着你在客户端改flink-conf.yaml,实际影响的是整个集群端行为。

3.2 向ResourceManager申请ApplicationMaster的过程

提交命令的核心逻辑在YarnClusterDescriptor这个类里。无论Session、Per-Job还是Application模式,都会走到同一段代码:向Yarn的ResourceManager申请一个新的Application。

这个过程可以拆成以下几步:

  1. 通过YarnClient创建一个新的Application,拿到一个ApplicationSubmissionContext。
  2. 在ApplicationSubmissionContext里封装本次提交的核心信息:容器规格(内存、CPU、磁盘)、启动命令、环境变量、本地资源(Jar包和配置文件)。
  3. 通过YarnClient.submitApplication()把Application提交给ResourceManager。
  4. ResourceManager把Application放入调度队列,等待某个NodeManager有空闲容器。

这里有一个重要概念:Flink作业提交时,Yarn层面首先拉起的是ApplicationMaster(AppMaster),并不是我们直观理解的JobManager。 在Application模式下,AppMaster和JobManager在同一个JVM进程里,但启动顺序有讲究——先启动Yarn相关的协议处理器,再启动Flink的组件。你可以理解为AppMaster是"壳",JobManager是"核",两者嵌套在一起。

3.3 AppMaster启动后Flink集群的初始化

Yarn在某个NodeManager上分配好容器后,会执行你在ApplicationSubmissionContext里配置的启动命令。这个命令通常长这样(从日志或yarn application -status里能看到):

BASH
java -Xmx... -cp ... org.apache.flink.yarn.entrypoint.YarnApplicationClusterEntryPoint

这里的YarnApplicationClusterEntryPoint就是Application模式集群端入口,它做几件事:

  • 初始化Akka/RPC系统。
  • 启动Dispatcher(负责接收作业提交、管理作业生命周期)。
  • 启动Flink自己的ResourceManager(负责任务槽的分配,跟Yarn的ResourceManager是两个不同的角色)。
  • 向Yarn的ResourceManager注册自己,申请后续启动TaskManager的容器。

如果你用的是Session模式,入口则是YarnSessionClusterEntryPoint;如果是Per-Job模式(旧版),入口是YarnJobClusterEntryPoint。三者启动的组件基本一致,差异在于JobGraph是外部提交还是集群内部生成。

这一步还有个小细节值得提:Application模式在集群端会先执行用户的main()方法,生成JobGraph后直接交给本地的Dispatcher,不需要再走网络传输。而Session和Per-Job模式是客户端生成JobGraph,再通过网络提交给Dispatcher。这就是Application模式能规避客户端环境问题的根本原因。

3.4 作业调度与TaskManager的启动

JobManager启动完成后,作业要真正跑起来,还得等TaskManager就位。这一步的交互逻辑是:

  1. Flink的ResourceManager(RM)向Yarn的ResourceManager发送容器请求,请求数量取决于作业的Slot需求。
  2. Yarn分配Container,在NodeManager上启动TaskManager进程。
  3. TaskManager进程启动后,反向向Flink的RM注册自己的Slot资源。
  4. RM把Slot分配给JobManager,JobManager真正调度Task到Slot上执行。

这套逻辑看起来简单,但牵扯的资源协调环节非常多。Yarn的RM只负责给容器,它不管Flink的Slot逻辑;Flink的RM只负责管理TaskManager的Slot,它不直接创建进程。两者通过"容器上下文"建立连接。任何一个环节慢一拍,都会表现为作业长时间处于SCHEDULED状态。

我在实际中经常碰到的场景是:作业提交完一直不跑,yarn application -status看到Container数量为0,JobManager日志里全是超时重试。最后定位下来,要么是Yarn队列资源不足,要么是申请的TaskManager内存超过了队列单容器限制。这种问题不看Yarn侧的指标基本发现不了。

3.5 一个核心区别:main()到底跑在哪个JVM里

三种模式在提交流程上最大的分水岭,就是main()方法的执行位置。Session和Per-Job模式下,客户端 -> 生成JobGraph -> 提交Dispatcher -> 调度执行这条链路里,客户端是工作链路的第一个节点。Application模式下,整个链路完全在Yarn集群内部闭合,客户端提交完就撒手了。

这对可靠性有实际影响。Per-Job模式下,如果客户端在执行main()的过程中挂了(比如本地JVM OOM、网络断了一下),作业压根不会提交上去。Application模式则不同,客户端只要把Jar和配置成功提交给Yarn,后续作业的启动完全由集群负责,客户端断了也不影响。

有一个场景让我印象深刻——之前用Per-Job模式跑一个数据接入作业,main()里要加载20MB的规则配置到内存,客户端是一台配置很低的跳板机,每次提交的时候客户端GC飙升,作业经常无故失败。后来迁移到Application模式,这个问题直接消失,因为main()跑在集群内,客户端只负责传文件。

4. 客户端与集群的通信链路:YarnClient、Runner与ActorSystem

Flink on Yarn极易踩坑的地方,是客户端跟集群之间的通信机制看不透。你以为提交完就完事了,其实后续还有一长串网络交互,任何一环断了都可能导致作业"离奇死亡"。

4.1 YarnClient与ApplicationSubmissionContext

客户端和Yarn ResourceManager的第一次通信,是通过Hadoop的YarnClient库完成的。YarnClient会读取Hadoop配置拿到RM地址,建立RPC连接,然后请求创建Application。

这里有个知识点值得理解一下:ApplicationSubmissionContext里不只是指定容器大小,还包含了一个ContainerLaunchContext(CLC)。 CLC里包含了环境变量(EnvVars)、本地资源列表(比如hdfs:///user/flink/flink-dist.jar、用户上传的作业Jar)、最终的启动命令。这些信息决定了AppMaster在NodeManager上怎么被拉起。

如果你用Yarn的web界面查看某个Application的详情,能看到完整的启动命令和环境变量,这就是CLC内容的直观体现。排查问题的时候可以重点翻一下,很多配置不到位的场景一眼就能看出来。

4.2 获取AppMaster地址与作业提交

AppMaster启动后,客户端需要知道"该跟谁说话"。这一步三种模式有差异:

  • Session模式:Session集群的JobManager地址在首次启动时就已经确定(通常是AppMaster运行的NodeManager的IP加一个随机端口),客户端连接的是这个固定地址。
  • Per-Job模式(旧):客户端提交作业到Yarn后,AppMaster启动,客户端通过Yarn的REST接口查询到JobManager的地址,然后往这个地址提交JobGraph。
  • Application模式:客户端不需要跟JobManager做任何通信。客户端提交给Yarn之后就返回,作业在集群内自行完成启动。

可能有人会问:Application模式下客户端完全不跟JobManager通信,那Web UI地址怎么看?答案是:通过Yarn的Application页面,点击ApplicationMaster的链接,Yarn会做代理转发,跳转到JobManager实际的Web端口。这个机制在生产排障时非常实用,不需要知道JobManager具体跑在哪台机器上,Yarn页面会帮你搞定。

4.3 三种模式下通信链路的差异

为了更直观地说明这个差异,我列一张对比表:

阶段 Session模式 Per-Job模式(旧) Application模式
客户端提交请求 需要,向RM申请 需要,向RM申请 需要,向RM申请
客户端获取JM地址 已知,连固定端口 通过Yarn REST查询 不需要
客户端提交JobGraph 需要,走REST 需要,走REST 不需要,集群内生成
客户端掉线的影响 作业可继续运行 作业可继续运行 作业可继续运行
main()执行位置 客户端 客户端 集群AppMaster

从这张表可以确认一件事:无论哪种模式,一旦JobGraph成功提交,客户端掉线都不会影响作业运行。 这就打破了很多人"客户端关掉Flink作业就挂"的直觉。作业的真正存活依赖的是AppMaster和TaskManager进程,客户端只是个入口。

4.4 网络异常时的表现与定位

通信链路长了,网络问题就多。我见过三种典型表现:

第一种,客户端提交时长时间卡在Submitting job...,最后一致报连接超时。这种情况通常是客户端到AppMaster之间网络不通。排查方法是先确认AppMaster跑在哪个NodeManager上,用telnet测试JobManager Web端口是否可达。如果是跨机房提交,记得检查防火墙和代理设置。

第二种,作业运行中Web UI能打开,但"JobManager地址"在Yarn页面上无法跳转。这通常是因为Yarn的Proxy端口(yarn.web-proxy.address)没配置或者配置错误。这个不影响作业运行,但影响排障效率,建议在生产环境提前配好。

第三种,客户端提交成功且作业也跑完了,但客户端一直卡着不退出。这是Flink客户端的常见行为:它会一直轮询Yarn的Application状态直到终态。如果你的脚本在flink run之后还要做其他事情,记得加-d参数(detached模式),让客户端提交完就返回。

5. Slot、容器与内存:Flink on Yarn的资源映射关系

资源管理是Flink on Yarn的重头戏,也是新手最容易被各种内存参数绕晕的地方。Flink的内存模型本来就不简单,到了Yarn上又叠加了一层容器概念,很多人直接懵圈。

5.1 TaskManager的容器化内存模型

在Yarn上,TaskManager是跑在一个容器里的。容器的大小由taskmanager.memory.process.size决定,这个参数就是你要向Yarn申请的总内存。但注意,这个总内存并不是全部给堆内存用,它内部要做一次拆分。

Flink的内存模型把进程内存分成这么几块:

  • JVM Heap:堆内存,又细分为Framework Heap和Task Heap。Task Heap是给算子计算用的,state的堆内存也在这里。
  • Managed Memory:托管内存,默认是堆外内存,主要用于RocksDB状态后端存储和批处理排序、哈希表。
  • Network Memory:网络缓冲内存,用于Shuffle和跨TaskManager的数据传输。
  • JVM Overhead:预留的JVM元空间、线程栈、本地代码缓存等额外开销。
  • Off-Heap(框架和任务):这部分是JVM堆外给框架和用户代码用的。

如果只设置taskmanager.memory.process.size = 8192m,Flink会按照默认比例做内存切分。但在生产环境里,我强烈建议根据作业类型手动指定关键参数。比如用RocksDB状态后端时,需要把taskmanager.memory.managed.size调大,否则RocksDB可能频繁写磁盘,性能大幅下滑。

这里有一个设计上很精妙的地方:Flink计算容器总内存时,会保证向Yarn申请的内存与实际进程需要的内存一致。 所以你在Yarn页面上看到的Containers内存,等于TaskManager进程配置的总内存。两边对得上,Yarn才能精准做资源评估和跨作业调度。

5.2 Slot与并发度的关系

Slot这个概念经常被误解为"一个Slot就是一份固定大小的内存"。其实是错的。Slot只是TaskManager内部用来限制并发Task数的逻辑单元,它并不做严格的内存隔离。 一个TaskManager里所有Slot共享同一个JVM,共享堆内存、托管内存、网络缓冲池。

在很多生产集群里会配置taskmanager.numberOfTaskSlots = 8甚至更高。比如一个TaskManager进程8GB内存,8个Slot,一个Task用到2GB内存,剩下的不一定会被卡住——因为内存是在JVM内自由分配的,Flink靠任务调度和内存池管理来尽量避免超用。

理解这点后,你就知道为什么Session模式多作业共享时风险那么高了:一个TaskManager的Slot给作业A,另外的Slot给作业B,虽然逻辑上隔离了"并发度",但物理上它们是同一个进程。作业A的内存一旦失控,作业B帮它一起承受后果。这就是资源隔离在Flink on Yarn上的本质局限。

5.3 关键参数与配置建议

以下是我在配置Flink on Yarn时经常调整的参数:

参数 作用 建议
jobmanager.memory.process.size JobManager总内存 一般2~4GB足够,除非并发作业非常多
taskmanager.memory.process.size TaskManager总内存(即Yarn容器大小) 根据状态规模和批流场景,一般8~32GB
taskmanager.numberOfTaskSlots 单TM的Slot数量 一般4~8,不宜过大
taskmanager.memory.managed.fraction 托管内存占比 默认0.4,RocksDB场景保持或调大
taskmanager.memory.network.fraction 网络缓冲占比 默认0.1,大量Shuffle场景建议调大
taskmanager.memory.jvm-overhead.fraction JVM额外开销占比 默认0.1,足够

容易忽略的是,不要同时设置taskmanager.memory.process.size和taskmanager.memory.flink.size,这俩一个指进程总内存,一个指Flink管理内存,同时设置会直接报错。你只需要指定其中一个,Flink会推导出另一个。

另外,记得确认Yarn侧的配额限制。假设Yarn队列的yarn.scheduler.maximum-allocation-mb是16GB,但你给TaskManager配了20GB,Yarn不会为这个容器分配资源,作业会一直卡在ACCEPTED状态。这种问题看Yarn队列配置比看Flink日志快得多。

6. 实操中的坑与排查经验

最后这部分聊聊我在生产环境里实际踩过的坑,很多是文档里不会写的,但遇到一次就能折腾你好几天。

6.1 怎么判断作业跑在哪种模式下

这个问题听起来基础,实际排查时确实有困惑的时候。最直接的方法是看Yarn页面上Application的名称:

  • Session模式下,多个作业的Application name是同一个(yarn-session那个名字),它们共享同一个ApplicationMaster。
  • Per-Job和Application模式下,一个作业就是一个独立的Application,名字通常是你的作业名。

另外,从Flink Web UI的地址上也能看出来:Session模式下JobManager地址固定,你每次提交作业都是访问同一个Web UI,但里面的Job列表会包含多个作业;Per-Job和Application模式下,每个作业有单独的JobManager进程和对应的Web UI地址。

6.2 容器被反复kill

容器被反复kill是最常见的疑难杂症。现象是:作业可以正常启动,但运行一会儿某个TaskManager就没了,然后又重新申请,过一会儿又没了,循环往复。

排查顺序建议遵循"由外向内"的原则:

  1. 先看Yarn页面上Container的日志,搜索Container killed by ApplicationMaster或者KillContainer关键字。
  2. 如果日志里出现"Exceeded physical memory limits"之类的报错,说明是NodeManager的虚拟内存检测触发了kill。这种情况在yarn.nodemanager.vmem-check-enabled=true时经常发生,排查时可以把虚拟内存和物理内存上报打开再看趋势。
  3. 如果日志里没有任何报错,TaskManager进程直接消失,大概率是物理内存超限被杀。这时候需要看Flink侧的GC日志和内存指标,确认是否存在内存配置不足。

我遇到过最隐蔽的一个坑是:TaskManager申请的容器内存16GB,但RocksDB的block cache和write buffer都用了默认值,一旦状态逐步增长,内存峰值轻松超过17GB,然后被NodeManager强杀。最后把托管内存调大、block cache显式配置,问题才解决。

6.3 日志不好找

Yarn模式下,Flink日志分散在容器所在节点的本地目录中。节点多的时候,你不可能一台一台去找。比较高效的方式是:

BASH
# 查看某个Application的全部日志
yarn logs -applicationId application_xxxxxxxxxxxx_xxx
 
# 只看某个容器的日志
yarn logs -applicationId application_xxxxxxxxxxxx_xxx -containerId container_xxxxxxxxxxxx_xxx

yarn logs依赖日志聚合功能(yarn.log-aggregation-enable=true),如果没开启,你只能通过Yarn的REST接口获取Container日志,或者登到对应NodeManager上翻logs/userlogs目录。

另外分享一个技巧:排查JobManager问题优先看launch_container.sh生成的日志,排查TaskManager问题优先看对应的Container日志,两者的目录层级和文件名不同,别搞混。

6.4 版本升级注意点

如果你正在从Flink 1.13或1.14升级到1.15+,特别注意Per-Job模式的废弃标记。Flink 1.15之后的版本里,虽然Per-Job模式依然能用,但官方在日志和文档里都会明确提醒"尽快迁移到Application模式"。

迁移本身不复杂:把flink run -m yarn-cluster改成flink run-application -t yarn-application,同时把main()里跟环境强相关的代码搬到集群内执行。有一点需要特别留意:Application模式下,客户端机器的Hadoop配置只影响提交过程,不影响作业运行;但作业真正需要的Hadoop依赖(比如HDFS写入)必须打包进用户Jar或者通过--provided-libs提供。

我自己迁移时还遇到过一个细节:Application模式下,作业代码里StreamExecutionEnvironment.getExecutionEnvironment()的Parallelism默认值是从集群配置文件里读的,客户端本地的flink-conf.yaml不再生效。如果集群端配置的默认并行度跟预期不一致,作业运行时的并发会跟着变。所以提交前务必确认flink-conf.yaml已经放到正确位置并被集群读取到,否则可能出现"作业跑是跑了,但资源占用不对"的诡异现象。

最后再补一个经验之谈:在排障Flink on Yarn问题时,别只盯着Flink的日志看,Yarn侧的信息往往更有价值。Application的状态流转(ACCEPTED -> RUNNING -> FINISHED/FAILED)能告诉你作业卡在哪一步,Container的分配情况能告诉你资源是否足够,AM和Container的日志能告诉你进程内部发生了什么。用好Yarn这个工具,Flink的很多疑难问题都能快速定位。