Hadoop Yarn作业提交全流程解析与排错指南
1. 为什么“提交作业”这件事比你想象中复杂十倍很多人第一次接触 Yarn 和 MapReduce以为只要写好 Mapper 和 Reducer调用hadoop jar命令作业就“提交成功”了——结果发现日志里卡在ACCEPTED状态半天不动或者直接报Application submission failed连 ApplicationMaster 都没拉起来。我带过的某高校大数据实训班里超过 65% 的学员在第二周卡在这个环节不是代码写错而是根本不知道自己敲下的那条命令背后到底触发了多少层调度、校验、序列化和状态跃迁。这根本不是一条命令的事而是一场横跨客户端、ResourceManager、NodeManager、HDFS、本地磁盘、JVM 进程的协同作战。Yarn 的“作业提交”四个字实际涵盖从用户视角的hadoop jar xxx.jar开始到 ApplicationMaster 完全接管任务调度为止的完整生命周期。它既不是 MapReduce 的专属流程也不是 Yarn 的内部黑盒——它是整个 Hadoop 生态中最常被跳过、最易被误解、但出问题时排查成本最高的关键链路。本文聚焦标题中的“全过程”不讲抽象架构图不堆概念术语只拆解真实环境中每一步发生了什么、谁在参与、数据怎么流动、失败点在哪、日志怎么看。关键词不是“Yarn”或“MapReduce”这种宽泛词而是ClientRMProtocol、AMLauncher、ContainerLaunchContext、LocalResource、ApplicationSubmissionContext、NMToken——这些才是你在yarn logs -applicationId输出里真正会撞见的实体。如果你正被Failed to submit application、NoClassDefFoundError in AM、Container exited with a non-zero exit code 143困扰这篇就是为你写的实战排错手册。提示本文所有路径、类名、配置项均基于 Apache Hadoop 3.3.6当前生产环境主流稳定版Hadoop 2.x 用户可对应参考org.apache.hadoop.yarn.client.api包路径变化但核心协议与状态机逻辑完全一致。2. 从hadoop jar到 ResourceManager客户端侧的七步拆解当你在终端输入hadoop jar wordcount.jar org.apache.hadoop.examples.WordCount /input /output表面看是执行一个 JAR实则客户端Client已悄然启动一套精密的提交流水线。这不是 shell 脚本的简单转发而是 Java 客户端通过 RPC 协议与 ResourceManagerRM完成一次强约束的契约协商。我们逐行还原这个过程重点标注每一步的不可省略动作和高频踩坑点。2.1 第一步解析 JAR 并构建 ApplicationSubmissionContext客户端首先加载wordcount.jar扫描其META-INF/MANIFEST.MF提取Main-Class这里是org.apache.hadoop.examples.WordCount。接着它不会直接运行这个类而是构造一个ApplicationSubmissionContext对象——这是整个提交流程的“宪法性文件”必须包含以下 7 个强制字段ApplicationId由 RM 分配客户端此时传null表示申请新 IDApplicationName默认取 JAR 名但可通过-Dmapreduce.job.nameMyJob覆盖Queue默认default但生产环境必须显式指定否则可能因队列权限拒绝AccessControlExceptionPriority整型值数值越小优先级越高注意不是越大越高AM Container ResourceResource对象定义 AM 所需内存MB和 vCores如memory1536, vCores1AM Launch Command关键格式为java -Xmx1024m org.apache.hadoop.mapreduce.v2.app.MRAppMaster ...其中...包含所有 MR 框架必需参数LocalResources一个MapString, LocalResource记录所有需分发到 NM 的文件包括job.jar你的业务 JARjob.xmlJobConf 序列化后的 XMLjob.splitInputSplit 元数据job.splitmetainfoSplit 元信息校验注意LocalResource不是文件路径而是 HDFS URI 时间戳 文件类型ARCHIVE/JAR/FILE 可见性PUBLIC/PRIVATE的四元组。若你手动修改job.xml后未更新时间戳RM 会拒绝该资源Invalid resource timestamp错误。2.2 第二步获取 RM 地址并建立 RPC 连接客户端读取core-site.xml中的fs.defaultFS如hdfs://mycluster和yarn-site.xml中的yarn.resourcemanager.address如rm1:8032。这里埋着第一个深坑如果yarn.resourcemanager.address指向的是 VIP 或 DNS 名而该地址在客户端机器上无法解析或网络不通错误日志只会显示Connection refused根本不会提示是 DNS 问题。我曾遇到某公司内网 DNS 缓存失效导致所有客户端提交失败排查三天才发现/etc/hosts里少了一行映射。连接建立后客户端调用ClientRMProtocol.submitApplication()方法。该 RPC 接口要求传入完整的ApplicationSubmissionContext且 RM 会做三重校验校验队列是否存在且用户有提交权限QueueNotFoundException/AccessControlException校验 AM 内存请求是否超过队列最大容量InvalidResourceRequestException校验LocalResources中所有 HDFS URI 是否可读FileNotFoundException若路径不存在AccessControlException若权限不足。2.3 第三步RM 接收并生成 ApplicationIdRM 收到请求后立即在内存中创建RMAppImpl实例并分配全局唯一ApplicationId格式application_1712345678901_0001。此时客户端日志会出现关键行INFO client.RMProxy: Connecting to ResourceManager at rm1/192.168.10.10:8032INFO mapreduce.JobSubmitter: number of splits:3INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1712345678901_0001注意job_1712345678901_0001是 MapReduce 框架生成的 JobID而application_1712345678901_0001是 Yarn 层的 ApplicationID二者通过job_前缀关联但不是同一个对象。很多初学者混淆这两者导致查日志时用错 ID。RM 将ApplicationId返回给客户端客户端随即更新本地Job对象的状态为RUNNING注意此时只是客户端状态AM 还未启动。2.4 第四步客户端上传资源到 HDFS staging 目录这是整个流程中最耗时也最易失败的环节。客户端将job.jar、job.xml、job.split等文件按LocalResource中声明的 HDFS URI上传至 RM 指定的 staging 目录默认hdfs:///user/$USER/.staging/application_1712345678901_0001/。上传过程并非简单put而是先创建 staging 目录需用户对/user/$USER/.staging有写权限对每个文件计算 MD5 校验和写入同目录下的.crc文件上传主文件同时设置replication10高可用集群通常设为 3但客户端默认按dfs.replication配置最后写入job.split的元数据文件job.splitmetainfo。常见失败场景磁盘空间不足客户端本地/tmp目录用于临时解压 JAR满报No space left on deviceHDFS 配额超限/user/$USER目录设置了spaceQuota上传失败报QuotaExceededException时间戳冲突同一 JAR 多次提交若未清理 staging 目录RM 可能拒绝重复资源ResourceAlreadyExistsException。实操技巧可通过hadoop fs -ls /user/$USER/.staging/查看 staging 目录内容若提交失败务必先hadoop fs -rm -r /user/$USER/.staging/application_*清理残留再重试。2.5 第五步客户端向 RM 发送最终提交确认当所有资源上传完毕客户端构造最终的SubmitApplicationRequest再次调用ClientRMProtocol.submitApplication()。这次请求携带的是已验证通过的ApplicationSubmissionContextRM 收到后正式将应用加入调度队列状态变为SUBMITTED。此时 YARN Web UIhttp://rm1:8088/cluster/apps中会出现该 Application状态显示SUBMITTEDApplication Type 为MAPREDUCE。这是第一个可观测里程碑——说明客户端工作已完成控制权移交 RM。2.6 第六步RM 调度器分配首个 ContainerAM ContainerRM 的调度器如 CapacityScheduler开始轮询检查SUBMITTED队列。它根据ApplicationSubmissionContext中声明的 AM 资源需求内存/vCores匹配空闲 NodeManagerNM。匹配成功后RM 向目标 NM 发送StartContainerRequest其中包含ContainerId格式container_e01_1712345678901_0001_01_000001e01 表示 AM ContainerContainerLaunchContext核心包含CommandsAM 启动命令java -Xmx1024m org.apache.hadoop.mapreduce.v2.app.MRAppMaster ...LocalResources指向 HDFS staging 目录中已上传的文件EnvironmentCLASSPATH必须包含$HADOOP_CONF_DIR:$HADOOP_COMMON_HOME/share/hadoop/common/*:...缺任何一项都会导致ClassNotFoundExceptionServiceData加密的NMToken用于 NM 验证该 Container 确由 RM 授权。关键原理NMToken是 RM 使用私钥签名的令牌NM 启动 Container 前必须用 RM 公钥验证。若集群启用了安全模式Kerberos此步骤失败会导致Invalid NMToken错误且日志中不会明说只报Container launch failed。2.7 第七步客户端轮询 RM 获取最终状态客户端并未退出而是启动一个后台线程以固定间隔默认 1 秒调用ClientRMProtocol.getApplicationReport()查询应用状态。当状态从SUBMITTED变为ACCEPTED说明 AM Container 已被 NM 接收并开始启动当变为RUNNING说明 AM 进程已在 NM 上成功exec并反向注册到 RM。此时hadoop jar命令才真正返回控制台输出INFO mapreduce.Job: The url to track the job: http://rm1:8088/proxy/application_1712345678901_0001/这个 URL 就是 MapReduce JobHistory Server 的代理入口不是 RM UI。3. ApplicationMaster 的诞生从 Container 启动到 MRAppMaster 初始化当 NodeManager 收到StartContainerRequest它的工作才刚开始。AM 的启动远非java -jar那么简单而是一套涉及进程隔离、资源监控、心跳保活、框架初始化的完整子系统。这一阶段的失败往往表现为 Application 卡在ACCEPTED状态数分钟然后自动失败为FAILED。3.1 NM 启动 Container 的底层机制NM 收到请求后首先在本地磁盘创建 Container 工作目录如/data/yarn/nm/usercache/$USER/appcache/application_1712345678901_0001/container_e01_1712345678901_0001_01_000001。然后执行以下操作下载 LocalResources从 HDFS staging 目录拉取job.jar、job.xml等文件到本地目录解压 ARCHIVE 类型资源如hadoop-mapreduce-client-app-3.3.6.jar会被解压到./work/子目录构建 CLASSPATH按ContainerLaunchContext中Environment字段拼接顺序至关重要$HADOOP_CONF_DIR$HADOOP_COMMON_HOME/share/hadoop/common/*$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/*./job.jar./work/*解压后的依赖执行启动命令bash -c java -Xmx1024m -Djava.security.krb5.conf/etc/krb5.conf ... org.apache.hadoop.mapreduce.v2.app.MRAppMaster注意-Xmx参数必须 ≤ContainerLaunchContext中声明的内存上限否则 NM 会在启动前 kill 进程报Container is running beyond physical memory limits。实测发现若声明 1536MB-Xmx设为 1200MB 最稳妥预留 300MB 给 JVM Metaspace 和 Native Memory。3.2 MRAppMaster 的四阶段初始化MRAppMaster主类启动后并非直接进入 MapReduce 逻辑而是经历严格的状态机初始化阶段一服务注册与心跳建立耗时 1s加载job.xml解析mapreduce.framework.nameyarn创建YarnRPC客户端连接 RM启动HeartbeatThread每秒向 RM 发送心跳AMRMProtocol.allocate()报告自身存活及资源需求。阶段二事件总线与服务启动耗时 1~3s初始化AsyncDispatcher事件总线启动核心服务JobHistoryEventHandler写 JobHistory 日志TaskAttemptListenerImpl监听 Task 启动请求ContainerAllocator向 RM 申请 Map/Reduce ContainerJobEndNotifier作业结束回调。阶段三Job 配置加载与 InputSplit 解析耗时取决于 split 数量从本地job.split文件反序列化FileSplit[]为每个 Split 创建MapTask对象计算 Reduce Task 数量mapreduce.job.reduces默认 1此阶段若job.split文件损坏会抛InvalidInputException状态卡在INITING。阶段四向 RM 申请第一个 Map Container关键跃迁点ContainerAllocator构造AllocateRequest请求 1 个 Container资源需求为memory2048, vCores1Map Task 默认配置RM 调度后返回AllocateResponse包含Container列表MRAppMaster状态从INITED变为RUNNING此时 Web UI 显示RUNNING并开始显示 Map 进度。实操心得若 AM 卡在INITED90% 是job.split问题。可登录 NM 机器进入 Container 工作目录用hadoop fs -cat /user/$USER/.staging/application_*/job.split | head -20查看前 20 行是否为合法的FileSplit二进制头SEQ字符串开头。若为乱码说明序列化失败。3.3 AM 与 RM 的心跳协议细节MRAppMaster的心跳不是简单 ping而是AllocateRequest/AllocateResponse的双向协商。每次心跳包含已分配 Container 列表告知 RM 哪些 Container 已启动成功已完成 Container 列表告知 RM 哪些 Container 已退出如 Map Task 完成资源请求列表ResourceRequest数组每个元素包含Priority数字越小越紧急Map Task 为 0Reduce Task 为 10ResourceName*表示任意节点/rack1表示机架host1表示具体主机Capability所需资源NumContainers请求数量。RM 在AllocateResponse中返回CompletedContainersStatuses已完成 Container 的退出码和诊断信息UpdatedNodesNM 状态变更如DECOMMISSIONEDAllocatedContainers新分配的 Container 列表。正是这套协议让 AM 能动态调整资源请求——例如 Reduce Task 启动前会将ResourceName从*改为host1实现数据本地性优化。4. 从 AM 到 TaskContainer 分配、启动与执行的完整闭环当MRAppMaster进入RUNNING状态真正的分布式计算才拉开序幕。但“启动一个 Map Task”绝非 AM 下达指令、NM 执行那么简单。它涉及跨进程通信、沙箱环境构建、JVM 参数定制、标准输出重定向等十余个环节。任何一环断裂都会导致 Task 卡住、崩溃或产生脏数据。4.1 AM 向 RM 申请 Map Container 的决策逻辑MRAppMaster并非盲目申请 Container而是基于InputSplit的位置信息进行智能调度。假设一个FileSplit的host列表为[host1, host2, host3]AM 的申请策略如下首选本地性向 RM 请求ResourceNamehost1Capabilitymemory2048,vCores1次选机架性若host1无资源则请求ResourceName/rack1最后任意性若机架也无资源则请求ResourceName*。这个过程在ContainerAllocator的makeRemoteRequest()方法中实现。你可以通过yarn logs -applicationId application_1712345678901_0001 | grep Asking for container查看 AM 的每次请求详情。注意mapreduce.input.fileinputformat.split.minsize和maxsize参数直接影响FileSplit数量。若设置不当如minsize10GB但文件仅 5GB会导致FileSplit数量为 0AM 无法申请任何 Map Container作业永远卡在RUNNING无进度。4.2 NM 启动 Map Task Container 的沙箱构建当 RM 将 Container 分配给 NMNM 执行与 AM Container 类似的流程但更严格创建独立沙箱目录/data/yarn/nm/usercache/$USER/appcache/application_1712345678901_0001/container_m01_1712345678901_0001_01_000001m01 表示 Map Container下载资源从 HDFS 拉取job.jar、job.xml并从 AM Container 的work/目录复制共享依赖避免重复下载构建 CLASSPATH比 AM 更复杂需额外添加./classes/用户自定义类./lib/*用户 JAR 的 lib 目录设置 JVM 参数-Xmx1536m -XX:UseParallelGC -Djava.io.tmpdir./tmp重定向标准流stdout和stderr被重定向到./stdout和./stderr文件供后续日志聚合。最关键的一步是ContainerExecutor的权限控制在非安全模式下使用DefaultContainerExecutor以$USER身份执行java命令在 Kerberos 安全模式下使用LinuxContainerExecutor必须配置yarn.nodemanager.container-executor.classorg.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor且/var/hadoop/yarn/local/usercache/$USER/appcache/目录需由yarn用户拥有否则报Permission denied。4.3 Map Task 的执行流程与生命周期Map Task 的主类是org.apache.hadoop.mapred.YarnChild它接收 AM 通过ContainerLaunchContext传递的参数job.jar路径job.xml路径split.index当前处理的 Split 序号taskid如task_1712345678901_0001_m_000000。执行流程如下加载 JobConf从job.xml构建JobConf对象实例化 InputFormat调用TextInputFormat的getSplits()获取FileSplit创建 RecordReaderLineRecordReader读取FileSplit指定范围内的文本行执行 Map 函数对每一行调用mapper.map()输出key,value对Spill 到磁盘当内存缓冲区io.sort.mb默认 100MB满排序后溢写到./spill0.outMerge Spill 文件所有溢写文件合并为./all-r-00000发送中间结果通过 HTTP POST 将all-r-00000上传到 Reduce Task 所在 NM 的ShuffleHandler服务。整个过程在YarnChild.main()中完成。若mapper.map()抛出未捕获异常YarnChild会捕获并打印堆栈到./stderr然后进程退出退出码为 1。NM 检测到退出码非 0会将 Container 状态设为COMPLETE并上报 RMRM 触发重试逻辑默认重试 4 次。4.4 Reduce Task 的特殊性Shuffle 阶段的网络挑战Reduce Task 的启动时机与 Map 不同它必须等待所有 Map Task 完成并上传all-r-00000文件后才能开始 Shuffle。因此Reduce Container 的启动请求通常在 Map 完成 80% 后才发出。Shuffle 阶段是性能瓶颈高发区涉及三个关键组件Map端ShuffleHandlerNM 内嵌的 Netty 服务端口默认 13562提供GET /mapOutput?job...reduce...map...接口Reduce端Fetcher线程池默认 5 个线程并发请求多个 Map 的输出网络层HTTP 传输无压缩除非开启mapreduce.reduce.shuffle.input.buffer.percent。常见问题Fetch failedReduce 请求 Map 输出时Map 所在 NM 已宕机报IOException: Connection refusedToo many fetch failures单个 Reduce Fetch 失败超 5 次作业失败Shuffle output lostMap Container 退出后all-r-00000文件被 NM 清理但 Reduce 还未拉取完。实操技巧可通过netstat -tuln | grep 13562确认ShuffleHandler是否监听若频繁 Fetch 失败检查mapreduce.reduce.shuffle.parallelcopies默认 5是否过小适当调大至 10 可提升吞吐。5. 全流程故障树从日志定位根因的标准化排查路径面对一个失败的 YarnMapReduce 作业90% 的人第一反应是看yarn logs -applicationId但日志量巨大无从下手。真正的高手会按状态码 → 组件 → 日志层级三级过滤快速定位根因。下面给出一套经过某公司 30 生产集群验证的标准化排查路径。5.1 状态码先行根据 Application 状态锁定问题域Application State可能根因关键日志位置典型错误码NEW客户端未完成提交客户端控制台NullPointerExceptioninJobSubmitterNEW_SAVINGRM 保存 Application 状态失败RM 日志 (yarn-root-resourcemanager-*.log)IOException: Failed to save app stateSUBMITTED资源上传失败或队列拒绝客户端日志 RM 日志QuotaExceededException,AccessControlExceptionACCEPTEDAM Container 启动失败NM 日志 (yarn-root-nodemanager-*.log)ExitCodeException,Container launch failedRUNNINGMap/Reduce Task 执行失败Container 日志 (yarn logs -applicationId ...)OutOfMemoryError,ClassNotFoundExceptionFINISHED作业成功JobHistory Server—提示yarn application -status application_1712345678901_0001是第一步必须先确认状态再决定查哪边日志。5.2 ACCEPTED 状态深度排查AM 启动失败的四大主因当 Application 卡在ACCEPTED说明 RM 已接受请求但 NM 未能成功启动 AM Container。此时必须登录分配到的 NodeManager 机器按顺序检查第一步检查 Container 工作目录是否存在ls -l /data/yarn/nm/usercache/$USER/appcache/application_1712345678901_0001/ # 若无 container_e01_* 目录说明 NM 未收到请求或拒绝执行第二步检查 NM 日志中的 Container 启动记录grep container_e01_1712345678901_0001 /var/log/hadoop-yarn/yarn-root-nodemanager-*.log # 查找 Starting container 和 Container exited with exit code第三步分析 Exit Code 含义核心Exit Code含义解决方案1Shell 命令语法错误检查ContainerLaunchContext.commands是否含非法字符137OOM Killer 杀死进程增加yarn.nodemanager.resource.memory-mb或降低yarn.scheduler.maximum-allocation-mb143kill -15优雅终止AM 启动超时yarn.app.mapreduce.am.job.client.timeout-ms默认 60000ms调大该值255ContainerExecutor 权限拒绝检查yarn.nodemanager.container-executor.class和/var/hadoop/yarn/local目录权限第四步验证 AM 启动命令的 CLASSPATH进入 Container 工作目录手动执行cat launch_container.sh复制java命令在 NM 机器上粘贴执行观察报错。这是最直接的验证方式。5.3 RUNNING 状态下的 Task 失败从 stderr 定位业务逻辑缺陷当 Application 状态为RUNNING但 Map/Reduce 进度停滞或失败需用yarn logs提取具体 Container 日志# 获取所有 Map Container 日志 yarn logs -applicationId application_1712345678901_0001 -containerId container_m01_1712345678901_0001_01_000001 map1.log # 关键查看三部分 grep Exception map1.log # 业务异常堆栈 grep GC map1.log # GC 日志判断是否内存泄漏 tail -n 50 map1.log # 结尾 50 行看最后执行动作典型业务问题案例java.lang.ArrayIndexOutOfBoundsExceptionmapper.map()中数组访问越界需检查输入数据格式java.sql.SQLExceptionMapper 中连接数据库但未配置hive-site.xml或 JDBC 驱动未放入job.jar/lib/org.apache.avro.AvroRuntimeExceptionAvro Schema 版本不匹配需确保job.jar中的 Avro 依赖与集群一致。注意yarn logs默认只聚合stdout/stderr若需查看syslogJVM GC 日志需加-log_files syslog参数。5.4 网络与配置类问题的交叉验证法许多问题表面是代码错误实则是配置或网络导致。推荐用“最小化验证法”剥离业务逻辑用hadoop-mapreduce-examples-3.3.6.jar中的pi例子提交hadoop jar $HADOOP_MAPRED_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar pi 10 100若pi成功说明集群基础功能正常问题在你的业务 JAR验证 HDFS 可读写在 NM 机器上执行hadoop fs -touchz /tmp/test_from_nm若失败说明 NM 与 HDFS 网络不通或 Kerberos 认证失败验证 YARN RPC 连通性在 NM 上执行telnet rm1 8032若不通检查yarn.resourcemanager.address配置和防火墙。这套方法论让我在某金融客户现场30 分钟内定位出因yarn.nodemanager.env-whitelist未包含LD_LIBRARY_PATH导致 JNI 调用失败的问题——而客户工程师已排查两天。6. 性能调优实战从提交延迟到任务吞吐的七项关键参数理解全流程后下一步是优化。YarnMapReduce 的性能瓶颈80% 集中在提交阶段和 Shuffle 阶段。以下是我在多个 PB 级集群中验证有效的七项关键参数调优每项都附带原理、默认值、推荐值和效果量化。6.1 客户端侧加速提交的三大参数参数默认值推荐值原理效果mapreduce.job.submit.file.replication103staging 目录文件副本数减少 HDFS 写放大提交时间缩短 40%尤其在高延迟网络下mapreduce.client.submit.file.buffer.size268435456(256MB)536870912(512MB)客户端上传 staging 文件的缓冲区大小减少write()系统调用次数提升吞吐yarn.client.failover-retries03客户端连接 RM 失败后的重试次数避免单点 RM 故障导致提交失败实操在mapred-site.xml中配置无需重启服务客户端下次提交即生效。6.2 AM 侧提升初始化与调度效率参数默认值推荐值原理效果mapreduce.am.max-attempts24AM 失败重试次数防止瞬时故障中断提升作业成功率尤其在资源紧张时yarn.app.mapreduce.am.resource.mb15362048AM 内存影响FileSplit解析速度InputSplit解析时间从 8s 降至 3s1000 Split 场景mapreduce.am.resource.cpu-vcores12AM vCores提升多线程处理能力ContainerAllocator并发请求能力翻倍6.3 Shuffle 侧突破网络 I/O 瓶颈参数默认值推荐值原理效果mapreduce.reduce.shuffle.parallelcopies510Reduce 端并发 Fetch 线程数Shuffle 阶段耗时降低 35%千节点集群