文章目录
-
- 前言
- [1. 先搞清楚这栋楼长什么样](#1. 先搞清楚这栋楼长什么样)
-
- [1.1 五层架构:一栋楼从门口到工位](#1.1 五层架构:一栋楼从门口到工位)
- [1.2 Application 模式 vs Per-Job 模式](#1.2 Application 模式 vs Per-Job 模式)
- [2. YARN 层面:AM 是怎么"生"出来的](#2. YARN 层面:AM 是怎么"生"出来的)
-
- [2.1 八步启动流程](#2.1 八步启动流程)
- [2.2 AM 启动命令长什么样](#2.2 AM 启动命令长什么样)
- [3. AM 内部:一个 JVM 的自我修养](#3. AM 内部:一个 JVM 的自我修养)
-
- [3.1 九步内部启动流程](#3.1 九步内部启动流程)
- [3.2 组件启动顺序:为什么一步都不能乱](#3.2 组件启动顺序:为什么一步都不能乱)
- [4. 核心类源码:挨个过堂](#4. 核心类源码:挨个过堂)
-
- [4.1 YarnApplicationClusterEntrypoint:入口类](#4.1 YarnApplicationClusterEntrypoint:入口类)
- [4.2 ClusterEntrypoint:模板方法模式现场](#4.2 ClusterEntrypoint:模板方法模式现场)
- [4.3 YarnResourceManager:资源包工头](#4.3 YarnResourceManager:资源包工头)
- [4.4 ApplicationDispatcherBootstrap:反射你的 main](#4.4 ApplicationDispatcherBootstrap:反射你的 main)
- [5. 整条链路:从一条命令到跑起来](#5. 整条链路:从一条命令到跑起来)
- [6. 配置、翻车现场和保命建议](#6. 配置、翻车现场和保命建议)
-
- [6.1 AM 关键配置参数](#6.1 AM 关键配置参数)
- [6.2 六个翻车现场](#6.2 六个翻车现场)
- [6.3 六条保命建议](#6.3 六条保命建议)
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程传送门http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
前言
凌晨两点,群消息响了:"作业挂了,帮我看看。"
你睡眼惺忪打开 YARN 界面,看到 AM 容器被杀了。说实话,你不太清楚一条命令是怎么变成一个进程的,只能盯着日志发呆。
别急,今天就把这条链从头到尾捋一遍。看完不敢保证你能当架构师,但下次背锅,你能背得有理有据。
1. 先搞清楚这栋楼长什么样
1.1 五层架构:一栋楼从门口到工位
整个系统分成五层:客户端、YARN、ApplicationMaster、Flink 作业组件、TaskManager。
你把它想象成一栋写字楼就行:客户端是前台,只管收快递;YARN 是物业,管工位和电费;AM 是你刚入职的工位;JobMaster 是项目经理;TaskManager 是楼下干活的实习生。
客户端层 :CliFrontend 解析 flink run-application 命令,YarnClusterDescriptor 负责部署集群,YarnClient 负责向 YARN 提交应用。客户端只上传 Jar、只提交,不执行你的 main------它就是前台,别指望它帮你干活,它连你的代码都不看一眼。
YARN 层:ResourceManager 管全局资源,分配 AM 容器和 TM 容器;NodeManager 负责启动、监控容器。说白了,物业只关心你在哪办公、电费交没交,至于你跑的是 Flink 还是别的,它才不管。
ApplicationMaster 层:这是 YARN 容器里跑的一个 JVM 进程,入口类是 YarnApplicationClusterEntrypoint,里面装着 YarnResourceManager、ApplicationDispatcher、WebMonitorEndpoint。一个 JVM 装下半个公司,比创业公司还紧凑。
Flink 作业组件层:AM 内部为每个作业启动 JobManagerRunner,JobManagerRunner 再启动 JobMaster,JobMaster 构建 ExecutionGraph 并调度 Task。注意,Application 模式下你的 main 是在 AM 端执行的------对,你的代码跑在别人家服务器上,像极了外包。
TaskManager 层:YARN 容器里跑的 TaskExecutor,入口类 YarnTaskExecutorRunner,管 Slot、执行 Task、向 RM 心跳上报。实习生干最多的活,拿最少的关注。
1.2 Application 模式 vs Per-Job 模式
这俩模式都为每个作业起独立集群,最大区别只有一件事:你的 main 在哪执行。
Per-Job 模式:客户端执行你的 main,构建好 JobGraph 再序列化传给 AM。相当于你去餐厅吃饭,还得自己拎着菜进后厨炒,炒完再端出来。
Application 模式:客户端只上传 Jar,main 在 AM 端执行,JobGraph 也在 AM 端构建。相当于你报个菜名,后厨全包了,你就负责等菜。
| 对比维度 | Application 模式(推荐) | Per-Job 模式(已废弃) |
|---|---|---|
| AM 入口类 | YarnApplicationClusterEntrypoint | YarnJobClusterEntrypoint |
| main 执行位置 | AM 端执行 | 客户端执行 |
| JobGraph 构建 | AM 端构建 | 客户端构建 |
| 客户端负载 | 低(只上传 Jar) | 高(构建 JobGraph) |
| Dispatcher | ApplicationDispatcher(单应用) | JobDispatcher(单作业) |
| 多作业支持 | 支持单应用内多作业 | 不支持 |
| 提交命令 | flink run-application -t yarn-application | flink run -t yarn-per-job |
Per-Job 又累又没人爱,Flink 1.15 直接把它废弃了。它就像那种还在跑的祖传代码:没人敢碰,但官方已经官宣"别用了"。
生产环境认准 Application 模式:客户端轻、能访问集群本地资源、支持多作业。以后谁再让你用 Per-Job,你先问问他是不是还想用 EJB。
2. YARN 层面:AM 是怎么"生"出来的
2.1 八步启动流程
第1步:客户端创建 YarnApplication
- YarnClusterDescriptor.deployApplicationCluster() 被调用
- 创建 YarnClient,连接 YARN ResourceManager
- 调用 yarnClient.createApplication() 获取新应用 ID(ApplicationId)
跟医院挂号似的,先领个号,没号后面全白搭。
第2步:上传资源到 HDFS
- 上传 Flink Jar(flink-dist.jar)到 HDFS 临时目录
- 上传用户 Jar 到 HDFS
- 上传配置文件(flink-conf.yaml、log4j.properties)到 HDFS
- 设置资源本地化(LocalResource):HDFS 路径、大小、时间戳、类型(FILE/ARCHIVE)
这就是托运,行李先走,人还没上飞机,箱子已经在地面等着了。
第3步:提交应用到 YARN RM
- 构建 ApplicationSubmissionContext
- 设置 AM 启动命令(ContainerLaunchContext)
- 设置 AM 资源(内存、CPU)
- 设置队列、优先级、应用名称
- 调用 yarnClient.submitApplication(context) 提交到 YARN RM
简历投出去了,能不能拿到面试通知,看缘分。
第4步:YARN RM 分配 AM 容器
- YARN RM 调度器(Capacity Scheduler / Fair Scheduler)接收应用
- 根据队列资源、优先级、用户限制调度
- 在某个 NodeManager 上分配 AM Container
- 向该 NM 发送 ContainerLaunch 事件
HR 终于看完了你的简历,开始给你分配工位。
第5步:NodeManager 启动 AM 容器
- NM 接收 ContainerLaunch 事件
- 从 HDFS 下载本地化资源(Jar、配置)
- 设置环境变量(JAVA_HOME、HADOOP_CONF_DIR、FLINK_CONF_DIR 等)
- 构建 AM 启动命令(java -cp ... YarnApplicationClusterEntrypoint ...)
- 启动 AM 容器进程(JVM)
物业开门、通电、喊你入职,工位擦得锃亮。
第6步:AM 注册到 YARN RM
- AM JVM 启动后,YarnResourceManager 创建 AMRMClient
- 调用 amrmClient.registerApplicationMaster(host, rpcPort, trackingUrl)
- 上报 Host、RPC 端口、Web UI 跟踪 URL
- YARN RM 将应用状态从 ACCEPTED 改为 RUNNING
入职第一件事:到人事部报到,不然系统里没你这号人。
第7步:AM 向 RM 申请 TM 容器
- YarnResourceManager 根据 Slot 需求创建 ContainerRequest
- 调用 amrmClient.addContainerRequest(containerRequest)
- 指定资源(内存、CPU)、优先级、节点标签
翻译成人话:跟老板要实习生,还指定了学历和身高。
第8步:YARN RM 分配 TM 容器,NM 启动 TM
- YARN RM 调度器分配 TM Container
- NM 下载资源,启动 TM 容器(YarnTaskExecutorRunner)
- TM 启动后向 Flink ResourceManager 注册 Slot
- JobMaster 部署 Task 到 TM Slot,作业开始运行
实习生到位,项目开工。前七步全是铺垫,就等这一步。
2.2 AM 启动命令长什么样
YARN 自动生成的 AM 容器启动命令大概长这样。不用背,但面试官一定会问:
bash
$JAVA_HOME/bin/java -Xmx1024m -Xms1024m \
-classpath $HADOOP_CONF_DIR:$FLINK_LIB_DIR/*:$USER_JAR \
-Dlog.file=$LOG_DIR/am.log \
-Dlog4j.configuration=file:$LOG_DIR/log4j.properties \
org.apache.flink.yarn.entrypoint.YarnApplicationClusterEntrypoint \
--configDir $FLINK_CONF_DIR \
--executionMode application \
--jobClassName com.example.MyJob \
--savepointPath hdfs:///savepoints/savepoint-xxx \
1> $LOG_DIR/am-stdout.log 2> $LOG_DIR/am-stderr.log
关键参数就四个:
--configDir:Flink 配置目录--executionMode application:执行模式(Application 模式)--jobClassName:用户作业入口类(Application 模式在 AM 端执行 main)--savepointPath:从保存点恢复(可选)
注意最后那两行重定向:标准输出和标准错误分开写。这是 YARN 版的"工作要留痕",出事了先翻 am-stderr.log,别一上来就对着 stdout 抓瞎。
3. AM 内部:一个 JVM 的自我修养
3.1 九步内部启动流程
第1步:JVM 启动,加载配置
- YARN 启动 AM JVM 进程
- 执行 YarnApplicationClusterEntrypoint.main(String\[\] args)
- 解析命令行参数(--configDir、--executionMode、--jobClassName 等)
- 加载 flink-conf.yaml 配置文件
- 合并动态配置(YARN 传入的 -D 参数)
起床第一件事:先看天气再决定穿什么。
第2步:创建 ClusterEntrypoint
- 创建 YarnApplicationClusterEntrypoint 实例
- 初始化 PluginManager(加载插件)
- 初始化 FileSystem(HDFS 等)
- 初始化 Security(Kerberos 认证)
- 初始化 MetricRegistry(指标注册)
穿好衣服、带好证件、戴上手表,一个都不能少。
第3步:启动 RPC 服务(AkkaRpcService)
- 创建 AkkaRpcService,启动 ActorSystem
- 绑定 RPC 地址(jobmanager.rpc.address,YARN 自动设为容器 hostname)
- 绑定 RPC 端口(jobmanager.rpc.port,默认 6123)
- 用于与 TaskManager、客户端、ResourceManager 通信
开机、联网、把手机调成响铃,不然别人找不着你。
第4步:启动 HighAvailabilityServices
- 根据 high-availability.type 配置创建 HA 服务
- ZooKeeper HA:创建 ZooKeeperClient、Leader 选举服务、BlobStore
- Kubernetes HA:创建 K8s Client、ConfigMap Leader 选举
- 用于 JobManager Leader 选举和 JobGraph 存储
给自己买保险。没买保险的 AM,挂了就是真挂了,连诈尸的机会都没有。
第5步:启动 ResourceManager(YarnResourceManager)
- 创建 YarnResourceManager 实例
- 创建 AMRMClient(连接 YARN RM,申请容器)
- 创建 NMClient(管理 TaskManager 容器)
- 启动 SlotManager(管理 Slot 分配)
- 向 YARN RM 注册 AM(registerApplicationMaster)
当上包工头,先跟大老板握手,再跟楼下施工队对接。
第6步:启动 Dispatcher(ApplicationDispatcher)
- 创建 ApplicationDispatcher(单应用 Dispatcher)
- 创建 JobManagerRunnerFactory(用于启动 JobManagerRunner)
- 初始化已恢复作业(从 HA 存储恢复)
- 启动 ApplicationDispatcherBootstrap(Application 模式特有,执行用户 main)
当上接线员,所有作业都得从你这转接。
第7步:启动 REST 端点(WebMonitorEndpoint)
- 创建 WebMonitorEndpoint(基于 Netty 的 REST 服务器)
- 绑定 rest.port(默认 8081)
- 注册所有 REST 处理器(作业提交、状态查询、指标、配置等)
- 启动 Web UI 静态资源服务
- 客户端通过 REST API 提交作业、查询状态
挂上招牌:欢迎光临,网页可查,童叟无欺。
第8步:执行用户 main 方法(Application 模式特有)
- ApplicationDispatcherBootstrap 通过反射执行用户 main(String\[\] args)
- 用户代码创建 StreamExecutionEnvironment
- 添加 Source、Transformation、Sink
- 调用 env.execute() 触发 StreamGraph→JobGraph 构建
- JobGraph 提交给 Dispatcher(本地提交,不走 REST)
你写了十年 Java,绝对想不到有朝一日你的 main 会被别人用反射调用,跟查户口似的:加载你的类、拿你的方法、直接 invoke。
第9步:启动 JobManagerRunner → JobMaster,作业运行
- Dispatcher 为每个作业创建 JobManagerRunnerImpl
- JobManagerRunner 进行 Leader 选举(HA 模式)
- Leader 选举成功后启动 JobMaster
- JobMaster 构建 ExecutionGraph(JobGraph→ExecutionGraph)
- 向 ResourceManager 申请 Slot
- TaskManager 注册 Slot 后,JobMaster 部署 Task 到 Slot
- Task 启动,从 Source 开始消费数据,作业正式运行
正式开工。前八步全是在搭台,第九步才轮到唱戏。
3.2 组件启动顺序:为什么一步都不能乱
arduino
PluginManager → FileSystem → Security → MetricRegistry → AkkaRpcService → HighAvailabilityServices → ResourceManager → Dispatcher → WebMonitorEndpoint → 用户 main → JobManagerRunner → JobMaster → TaskManager 注册 → 作业运行
为什么是这个顺序?一句话:谁被依赖,谁先上。
- 基础组件(Plugin/FileSystem/Security/Metric)必须最先启动,后面全指望它们,像水电燃气。
- AkkaRpcService 必须在 HA 和 RM 之前,因为它俩都要打电话。
- HA 服务必须在 RM 和 Dispatcher 之前,因为需要 Leader 选举和 JobGraph 存储。
- ResourceManager 必须在 Dispatcher 之前,因为 Dispatcher 启动 JobMaster 后要向 RM 申请 Slot。
- Dispatcher 必须在 REST 端点之前,因为 REST 端点要拿 Dispatcher 交作业。
- 用户 main 在 Dispatcher 启动后执行,因为要有人接住 JobGraph。
- JobMaster 等用户 main 提交 JobGraph 后才启动。
- TaskManager 在 RM 启动后注册,JobMaster 才能部署 Task。
这个顺序比你去医院体检的路线还严格,乱一步整栋楼就塌。你敢把 Dispatcher 提到 RM 前面,JobMaster 起来发现没人给 Slot,当场表演一个"要啥没啥"。
4. 核心类源码:挨个过堂
4.1 YarnApplicationClusterEntrypoint:入口类
java
public class YarnApplicationClusterEntrypoint extends YarnJobClusterEntrypoint {
public static void main(String[] args) {
// 1. 解析命令行参数
final CommandLineParser<YarnApplicationClusterConfiguration> parser =
new CommandLineParser<>(new YarnApplicationClusterConfigurationParserFactory());
YarnApplicationClusterConfiguration configuration = null;
try {
configuration = parser.parse(args);
} catch (Exception e) {
System.exit(1);
}
// 2. 安装默认文件系统
installDefaultFileSystem(configuration.getConfigDir());
// 3. 加载配置
final Configuration config = loadConfiguration(configuration.getConfigDir());
// 4. 创建并启动 ClusterEntrypoint
final YarnApplicationClusterEntrypoint entrypoint =
new YarnApplicationClusterEntrypoint(config, configuration);
entrypoint.startCluster();
}
@Override
protected Dispatcher createDispatcher(
Configuration configuration,
RpcService rpcService,
HighAvailabilityServices haServices,
ResourceManagerGateway resourceManagerGateway,
BlobServer blobServer,
HeartbeatServices heartbeatServices,
JobManagerMetricGroup jobManagerMetricGroup,
ExecutorService executorService) throws Exception {
// 创建 ApplicationDispatcher(单应用)
final ApplicationDispatcherBootstrap bootstrap =
new ApplicationDispatcherBootstrap(
applicationConfiguration.getJobClassName(),
applicationConfiguration.getProgramArgs(),
applicationConfiguration.getSavepointPath());
return new ApplicationDispatcher(
rpcService,
haServices.getDispatcherLeaderElection(),
...
bootstrap);
}
@Override
protected ResourceManager<?> createResourceManager(
Configuration configuration,
ResourceID resourceId,
RpcService rpcService,
HighAvailabilityServices haServices,
HeartbeatServices heartbeatServices,
...) throws Exception {
// 创建 YarnResourceManager
return YarnResourceManager.create(
configuration,
resourceId,
rpcService,
haServices,
heartbeatServices,
...);
}
}
关键逻辑:
- main 方法就是 AM 进程的入口:解析参数、装默认文件系统、加载配置、创建并 startCluster,一气呵成。
- createDispatcher 创建 ApplicationDispatcher,顺手塞一个 ApplicationDispatcherBootstrap------这个 bootstrap 就是专门执行你 main 的"工具人"。
- createResourceManager 创建 YarnResourceManager,负责跟 YARN RM 申请容器。
- 它继承自 YarnJobClusterEntrypoint,把 YARN 通用逻辑全复用了一遍。能继承的绝不重写,这觉悟值得大多数程序员学习。
4.2 ClusterEntrypoint:模板方法模式现场
java
public abstract class ClusterEntrypoint implements AutoCloseable {
private final Configuration configuration;
private PluginManager pluginManager;
private FileSystem fileSystem;
private RpcService rpcService;
private HighAvailabilityServices haServices;
private ResourceManager<?> resourceManager;
private Dispatcher dispatcher;
private WebMonitorEndpoint<?> webMonitorEndpoint;
public void startCluster() throws Exception {
try {
// 1. 初始化基础组件
pluginManager = PluginUtils.createPluginManager(...);
FileSystem.initialize(configuration, pluginManager);
SecurityUtils.install(new SecurityConfiguration(configuration));
MetricRegistry metricRegistry = createMetricRegistry(configuration);
// 2. 启动 RPC 服务
rpcService = createRpcService(configuration);
// 3. 启动 HA 服务
haServices = createHaServices(configuration, rpcService);
// 4. 启动 BlobServer(Jar 存储)
BlobServer blobServer = new BlobServer(configuration, haServices.createBlobStore());
// 5. 启动 ResourceManager
resourceManager = createResourceManager(...);
resourceManager.start();
// 6. 启动 Dispatcher
dispatcher = createDispatcher(...);
dispatcher.start();
// 7. 启动 REST 端点
webMonitorEndpoint = createRestEndpoint(...);
webMonitorEndpoint.start();
} catch (Exception e) {
throw new FlinkException("Could not start cluster entrypoint.", e);
}
}
// 抽象方法,由子类实现
protected abstract Dispatcher createDispatcher(...);
protected abstract ResourceManager<?> createResourceManager(...);
protected abstract WebMonitorEndpoint<?> createRestEndpoint(...);
}
关键设计:
- startCluster 把标准的七步启动流程写死,模板方法模式的教科书现场。
- 基础组件最先,然后 RPC → HA → BlobServer → ResourceManager → Dispatcher → REST 端点,顺序锁死。
- createDispatcher / createResourceManager / createRestEndpoint 全是抽象方法,留给子类填空。
- 跟考试填空题一样:框架给你,答案自己填,填错全校通报------启动失败抛异常,AM 进程退出,YARN 标记应用失败。
4.3 YarnResourceManager:资源包工头
java
public class YarnResourceManager extends ActiveResourceManager<YarnWorkerNode> {
private final AMRMClient<ContainerRequest> amrmClient;
private final NMClient nmClient;
private final int taskManagerMemory;
private final int taskManagerCPU;
public static YarnResourceManager create(...) throws Exception {
// 1. 创建 AMRMClient(连接 YARN RM)
AMRMClient<ContainerRequest> amrmClient = AMRMClient.createAMRMClient();
amrmClient.init(conf);
amrmClient.start();
// 2. 创建 NMClient(管理 TM 容器)
NMClient nmClient = NMClient.createNMClient();
nmClient.init(conf);
nmClient.start();
return new YarnResourceManager(..., amrmClient, nmClient);
}
@Override
protected void initialize() throws ResourceManagerException {
// 3. 向 YARN RM 注册 AM
amrmClient.registerApplicationMaster(
rpcService.getAddress(),
rpcService.getPort(),
webMonitorUrl);
}
@Override
public CompletableFuture<Void> requestNewWorker(WorkerResourceSpec workerResourceSpec) {
// 4. 向 YARN RM 申请 TM 容器
final ContainerRequest containerRequest = new ContainerRequest(
Resource.newInstance(taskManagerMemory, taskManagerCPU),
null, // 节点(null 表示任意节点)
null, // 机架
Priority.newInstance(0));
amrmClient.addContainerRequest(containerRequest);
return CompletableFuture.completedFuture(null);
}
@Override
public void onContainersAllocated(List<Container> containers) {
// 5. YARN RM 分配容器回调
for (Container container : containers) {
// 6. 在 NM 上启动 TM 容器
ContainerLaunchContext ctx = createTaskManagerLaunchContext();
nmClient.startContainer(container, ctx);
}
}
}
关键逻辑:
- create 创建 AMRMClient(连 YARN RM)和 NMClient(管容器):一个向上要资源,一个向下发任务。中间商不赚差价,只负责跑腿。
- initialize 里 registerApplicationMaster 报到,正式在 YARN 挂上号。
- requestNewWorker 构造 ContainerRequest,内存 CPU 写死,节点和机架留 null------意思是不挑地方,哪都行。
- onContainersAllocated 是分配回调,容器到手直接 startContainer。人狠话不多,拿到容器就开工。
- TM 启动后向 Flink ResourceManager 注册 Slot,资源闭环完成。
4.4 ApplicationDispatcherBootstrap:反射你的 main
java
public class ApplicationDispatcherBootstrap implements DispatcherBootstrap {
private final String jobClassName;
private final String[] programArgs;
private final String savepointPath;
public ApplicationDispatcherBootstrap(
String jobClassName,
String[] programArgs,
String savepointPath) {
this.jobClassName = jobClassName;
this.programArgs = programArgs;
this.savepointPath = savepointPath;
}
@Override
public void initialize(DispatcherGateway dispatcherGateway, FatalErrorHandler fatalErrorHandler) {
try {
// 1. 加载用户作业类
Class<?> mainClass = Class.forName(
jobClassName, true, Thread.currentThread().getContextClassLoader());
// 2. 获取 main 方法
Method mainMethod = mainClass.getMethod("main", String[].class);
// 3. 通过反射执行用户 main 方法
// 用户 main 内部会调用 env.execute(),构建 JobGraph 并提交给 Dispatcher
mainMethod.invoke(null, (Object) programArgs);
} catch (Exception e) {
fatalErrorHandler.onFatalError(
new FlinkException("Failed to execute application main method.", e));
}
}
}
关键逻辑:
- initialize 在 Dispatcher 启动后被调用,等一切就绪才轮到你上场。
- Class.forName 加载你的类,getMethod 拿 main,mainMethod.invoke(null, args) 反射执行,一步到位。
- 你的 main 内部创建 StreamExecutionEnvironment,env.execute() 构建 StreamGraph→JobGraph,然后通过本地 DispatcherGateway 提交------不走 REST,直接内存里交接,连快递费都省了。
- main 挂了就调 fatalErrorHandler,AM 进程退出。你的 main 就是全村的希望:它行,作业就行;它炸,全村陪葬。
5. 整条链路:从一条命令到跑起来
arduino
客户端 flink run-application → YarnClusterDescriptor.deployApplicationCluster → YarnClient.submitApplication → YARN RM 分配 AM Container → NodeManager 启动 AM JVM → YarnApplicationClusterEntrypoint.main → ClusterEntrypoint.startCluster(7步) → AkkaRpcService → HAServices → YarnResourceManager → ApplicationDispatcher → WebMonitorEndpoint → ApplicationDispatcherBootstrap.initialize(执行用户 main) → 用户 env.execute() → JobGraph 提交 → Dispatcher 启动 JobManagerRunner → JobMaster 构建 ExecutionGraph → YarnResourceManager 申请 TM 容器 → TaskManager 注册 Slot → JobMaster 部署 Task → 作业运行
数一数,这一条链 17 个环节,比你双十一的快递链路还长。
而且快递丢了最多退款,这条链上任何一环断了,你收到的不是钱,是一封 "Application failed" 的邮件,外加群里那句:"帮忙看看。"
6. 配置、翻车现场和保命建议
6.1 AM 关键配置参数
yaml
# AM 进程总内存(生产建议 2-4G)
jobmanager.memory.process.size: 2048m
# AM RPC 地址(YARN 自动设置为容器 hostname)
jobmanager.rpc.address: auto
# AM RPC 端口
jobmanager.rpc.port: 6123
# REST API / Web UI 端口
rest.port: 8081
# REST 绑定端口范围(避免端口冲突)
rest.bind-port: 8081-8090
# HA 类型(zookeeper/kubernetes/none)
high-availability.type: zookeeper
# ZooKeeper 集群地址
high-availability.zookeeper.quorum: zk1:2181,zk2:2181,zk3:2181
# HA 元数据存储路径
high-availability.storageDir: hdfs:///flink/ha
# YARN 队列名称
yarn.application.queue: default
# YARN 应用名称
yarn.application.name: Flink App
# 每个容器虚拟核数
yarn.containers.vcores: auto
# YARN 应用优先级
yarn.application.priority: 0
# Blob 服务端口(Jar 传输)
blob.server.port: 随机
# Blob 本地存储目录
blob.storage.directory: /tmp/blob
# 故障恢复策略(region/full)
jobmanager.execution.failover-strategy: region
这么多参数,记住三件事就够:
- jobmanager.memory.process.size 生产给 2-4G,别抠。
- rest.bind-port 留个范围 8081-8090,不然端口打架。
- HA 必须配。没 HA 的 AM,挂了就是真挂了,连诈尸的机会都没有------这话我说第二遍了,说明真的很重要。
6.2 六个翻车现场
1. AM 容器启动失败(Container killed by YARN)
原因:AM 内存超 YARN 容器限制,或 AM 进程 OOM 被 YARN 杀了。
解法:调大 jobmanager.memory.process.size,检查 YARN 容器内存限制,翻 AM 日志找 OOM。
翻译:给工位换张更大的桌子。
2. AM 启动后无法连接 YARN RM
原因:YARN 配置错误、Kerberos 认证失败、网络不通。
解法:检查 yarn-site.xml、确认 Kerberos ticket、检查网络连通性,翻 AM 日志看连接错误。
翻译:报到那天人事部没人,你在大厅干等。
3. 用户 main 方法执行失败(ClassNotFoundException)
原因:用户 Jar 没上传到 HDFS,或依赖缺失,或加载顺序错误。
解法:确认 Jar 上传,用 maven-shade 打包,设置 classloader.resolve-order=child-first。
翻译:菜谱里写的食材,后厨一样都没有。
4. REST API 无法访问(Connection refused)
原因:rest.port 被占、AM 没绑到正确地址、防火墙拦截。
解法:设置 rest.bind-port 端口范围、检查 rest.address、检查防火墙规则。
翻译:招牌挂了,但客人进不来。
5. TaskManager 无法注册到 ResourceManager
原因:TM 连不上 AM RPC 地址、Slot 不匹配、TM 内存不足。
解法:检查 jobmanager.rpc.address、确认 TM 网络可达、检查 taskmanager.numberOfTaskSlots、加大 TM 内存。
翻译:实习生到楼下报到,前台说系统里没这人。
6. ZooKeeper Leader 选举失败
原因:ZK 连接失败、session 超时、ACL 权限问题。
解法:检查 high-availability.zookeeper.quorum、确认 ZK 集群正常、检查 zk-path-prefix 和 ACL。
翻译:几个领导抢一个话筒,谁也没抢到。
六个问题凑齐,可以召唤运维了。其实大半是配置和网络的事,跟写代码没半毛钱关系------你只是那个负责背锅的。
6.3 六条保命建议
- 合理设置 AM 内存:生产建议 2-4G,大作业或多作业的 Application 模式适当加大,别让 AM OOM 带崩整个应用。
- 配置 HA 高可用:生产必须配 ZooKeeper 或 Kubernetes HA,设好 high-availability.storageDir,确保 AM 挂了能自己爬起来。
- 使用 Application 模式:客户端轻量化,main 在 AM 端执行,Per-Job 已废弃,别再用它感动自己。
- 配置 REST 端口范围:rest.bind-port=8081-8090,避免端口冲突;生产环境加负载均衡或反向代理访问 Web UI。
- 监控 AM 日志和指标:盯 YARN container logs、JVM 内存、GC、线程数,Prometheus 安排上。
- 配置 YARN 队列和优先级:重要作业用独立队列,设 yarn.application.priority,别跟别人抢资源。
最后总结一句人话:YARN 管"在哪跑",Flink 管"跑什么",分层协作,谁也不越界------像极了产品经理和研发互相甩锅,但最后一起背锅。
理解这条启动链路,最大的收获不是面试能吹,而是作业挂了的时候,你能在五分钟内定位到是哪个环节的问题,而不是对着 YARN 界面发呆半小时,最后截图发群里,配一句:"像是网络问题。"
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 传送门http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。