
1. 项目概述为什么必须搞懂 Standalone 和 Yarn 这两种 Spark 运行模式在某高校大数据实验室带学生做实时日志分析项目时我遇到过一个典型场景团队用同一份 Spark Streaming 代码在本地开发环境跑得飞快一上生产集群就频繁 OOM、任务卡在 ACCEPTED 状态、Executor 启动失败——排查三天最后发现根本不是代码问题而是提交命令里--master参数写成了spark://master:7077而集群实际走的是 Yarn 资源调度。这种“模式错配”导致的故障在 Spark 初学者和中小团队中发生率极高远超代码逻辑错误。Standalone 模式和 Yarn 模式不是可有可无的配置选项而是 Spark 应用生命周期的底层操作系统级选择。它决定了资源如何申请、Executor 如何部署、Driver 运行在哪、日志存到哪、甚至故障恢复策略怎么生效。你选 Standalone就得自己搭 Master/Worker 进程、管节点心跳、调内存参数你选 Yarn就要理解 ApplicationMaster 的角色、Container 生命周期、Yarn 的队列权限控制。很多人以为“Spark 就是写 RDD 或 DataFrame”但真正决定一个 Spark 作业能不能上线、稳不稳定、扩不扩容的恰恰是运行模式这一层。本文不讲抽象概念只拆解真实生产环境中这两种模式的启动链路、资源流转图、关键配置项背后的物理意义以及我在某电商实时推荐系统迁移过程中踩过的 7 个典型坑——比如 Standalone 下spark.executor.memory实际被 Worker JVM 堆外内存吃掉 20%或者 Yarn 模式下--num-executors设为 100 却只起了 32 个背后全是 Yarn 队列资源硬限制在起作用。如果你正在准备大数据面试、要上线第一个 Spark 生产任务、或是刚接手一个跑在旧 Standalone 集群上的遗留系统这篇就是你该花两小时精读的实操手册。2. 核心设计逻辑与模式选型依据不是技术偏好而是架构约束2.1 Standalone 模式轻量自治适合“可控封闭”环境Standalone 是 Spark 自带的原生集群管理器它的设计哲学非常直白用最少的外部依赖实现最直接的资源控制。整个架构只有三个核心进程Master主节点、Worker工作节点、Driver应用驱动器。Master 负责接收应用提交、分配 Worker 资源、监控 Worker 心跳Worker 负责启动 Executor 进程、汇报资源使用Driver 则在客户端机器或集群内某节点上运行负责任务调度和 DAG 切分。这种“全家桶”式设计意味着你不需要提前部署 Hadoop、Yarn 或 Kubernetes只要在几台机器上解压 Spark 包、改好conf/spark-env.sh里的SPARK_MASTER_HOST和SPARK_WORKER_MEMORY执行sbin/start-master.sh和sbin/start-worker.sh集群就起来了。我曾帮某制造企业搭建边缘计算节点上的实时质量检测系统现场只有 4 台工业 PC无法装 HadoopStandalone 就是唯一选择——它把资源调度逻辑压缩进 Spark 自身省去了跨组件协调的复杂性。但代价也很明显Master 是单点虽支持 ZooKeeper 做高可用但切换过程会中断新应用提交Worker 资源隔离靠 OS 进程没有容器级隔离一个 Executor 内存溢出可能拖垮整个 Worker JVM更关键的是它完全不感知 HDFS 权限、Hive Metastore 认证、Kerberos 安全框架——这些在金融、政务类客户环境中是硬性要求。所以 Standalone 的适用边界非常清晰小规模50 节点、非生产核心业务、无强安全合规需求、运维人力有限的场景。它不是“过时”而是“精准匹配”。2.2 Yarn 模式融入生态面向“企业级混合负载”治理Yarn 模式本质是 Spark 向 Hadoop 生态的深度归顺。它把 Spark 的资源调度权完全交给 Yarn 的 ResourceManagerRM和 NodeManagerNM自身只保留计算引擎能力。当你执行spark-submit --master yarn --deploy-mode cluster时流程是这样的Client 进程先向 RM 申请一个 Container 启动 ApplicationMasterAM这个 AM 是 Spark 自己写的轻量级进程它不干计算只做三件事——向 RM 申请更多 Container 来运行 Executor、跟 Driver此时 Driver 就在 AM 所在 Container 内通信、向 RM 汇报应用状态。所有 Executor 都运行在 Yarn 分配的独立 Container 中每个 Container 有明确的 CPU Core 数、内存上限由yarn.nodemanager.resource.memory-mb控制且受 Yarn 队列Queue的 ACL 权限和资源配额约束。这意味着什么第一资源复用率高Yarn 集群上可以同时跑 MapReduce、Hive、Presto、FlinkSpark 只是其中一个“租户”按需申请资源不用独占节点第二安全合规落地Yarn 支持 Kerberos 认证、LinuxContainerExecutorLCE做用户隔离、队列 ACL 控制谁能在哪个队列提交任务第三运维体系统一日志自动聚合到 HDFS、监控指标通过 Yarn REST API 对接 Prometheus。我在某省级政务云平台做数据中台升级时原有 Hive 批处理和新上的 Spark 实时 ETL 必须共用一套 Yarn 集群否则要维护两套资源池成本翻倍。Yarn 模式让 Spark “隐身”成 Yarn 的一个计算框架而不是另起炉灶。但它也有硬伤启动链路长Client → RM → AM → RM → Executor首次任务延迟比 Standalone 高 3~5 秒AM 本身是个单点虽可通过yarn.resourcemanager.am.max-attempts配置重试但重试期间任务就卡住更麻烦的是Yarn 的 Container 内存模型和 Spark 的 Executor 内存模型存在“双重计算”——Yarn 分配的 Container 内存 Executor 堆内存 堆外内存 JVM 开销而 Spark 默认只按spark.executor.memory配堆内存剩下部分全靠经验估算。这直接导致很多团队在 Yarn 上调参像玄学。2.3 模式选型决策树一张表看穿本质差异维度Standalone 模式Yarn 模式部署复杂度极低仅 Spark 包 Java5 分钟启集群高需完整 Hadoop 集群HDFS Yarn至少 30 分钟初始化资源隔离强度弱OS 进程级Worker JVM 内多 Executor 共享堆外内存强Yarn Container 级CPU/Memory 硬限制支持 cgroups多框架共存不支持Spark 独占集群资源天然支持与 MR/Hive/Flink 共享 Yarn 资源池安全认证能力无仅基础 Basic Auth不支持 Kerberos/LDAP完整集成 Yarn Kerberos、队列 ACL、用户白名单日志与监控分散各 Worker 本地日志需额外搭 ELK统一Yarn 日志聚合到 HDFSMetrics 直接对接 Grafana故障恢复粒度粗Master 故障影响所有新任务Worker 故障导致其上所有 Executor 丢失细AM 故障可重试单个 Container 失败不影响其他 Executor适用规模小型≤ 20 节点≤ 100 并发任务中大型100 节点千级并发多租户混部提示别被“Standalone 更简单”误导。简单是相对部署而言但长期运维中Yarn 的统一治理能力能省下 70% 的排障时间。我们曾统计某金融客户一年的 Spark 故障工单Standalone 集群中 68% 是资源争抢导致的 Executor OOM而 Yarn 集群同类故障仅 12%因为 Yarn 的 Container 内存硬限制让问题暴露在申请阶段而非运行时崩溃。3. Standalone 模式深度解析从启动到故障的全链路实操3.1 集群启动与核心配置文件详解Standalone 集群的启动看似简单但conf/目录下几个文件的每一行都决定着集群生死。以 Spark 3.4.2 为例关键配置如下conf/spark-env.sh这是环境变量总开关必须设置export JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 # 必须与 Worker 一致 export SPARK_MASTER_HOST192.168.1.10 # Master 绑定 IP不能写 localhost export SPARK_MASTER_PORT7077 # Master RPC 端口默认 7077 export SPARK_MASTER_WEBUI_PORT8080 # Web UI 端口默认 8080 export SPARK_WORKER_CORES4 # 每个 Worker 最大可用 CPU Core 数 export SPARK_WORKER_MEMORY8g # 每个 Worker 总内存注意是 Worker 进程内存非 Executor export SPARK_WORKER_PORT7078 # Worker RPC 端口避免冲突 export SPARK_WORKER_WEBUI_PORT8081 # Worker UI 端口注意SPARK_WORKER_MEMORY是 Worker JVM 的最大堆内存它要容纳 Worker 自身开销 所有 Executor 的堆外内存如 Netty 缓冲区、Unsafe 操作内存。实测发现当 Worker 内存设为 8g启动 4 个 Executor各 2g 堆内存时Worker JVM 常驻内存达 9.2g超出部分来自堆外。因此建议SPARK_WORKER_MEMORY至少比所有 Executor 堆内存总和高 20%。conf/slaves纯文本文件每行一个 Worker 节点主机名或 IP例如node1.example.com node2.example.com node3.example.com启动时sbin/start-slaves.sh会 SSH 到这些节点执行start-worker.sh。务必确保 Master 节点能免密 SSH 到所有 Worker这是新手最容易卡住的点。我见过三次故障都是因为slaves文件里写了localhost结果 Worker 启在本地Master 却在另一台机器网络不通。conf/spark-defaults.conf全局默认配置对所有应用生效关键项spark.master spark://192.168.1.10:7077 # 默认提交到本集群 spark.eventLog.enabled true # 启用事件日志用于 Web UI 重放 spark.eventLog.dir hdfs://namenode:8020/spark-logs # 日志存 HDFS避免本地磁盘满 spark.serializer org.apache.spark.serializer.KryoSerializer # Kryo 比 Java 序列化快 10 倍3.2 应用提交全流程与 Driver 位置陷阱Standalone 模式下spark-submit的--deploy-mode有两个取值client和cluster它们决定了 Driver 的命运。Client 模式默认Driver 进程运行在提交命令的客户端机器上。执行spark-submit --master spark://master:7077 --class com.example.Job app.jar时你的笔记本就是 Driver。好处是调试方便System.out.println直接打到终端坏处是网络脆弱性致命——如果客户端网络断开Driver 进程退出整个应用立即失败Executor 变成孤儿进程虽然还在跑但没人调度新任务。我在某车载设备数据回传项目中吃过亏工程师用笔记本提交 Spark Streaming 任务到车间服务器集群结果笔记本休眠 5 分钟任务就挂了日志里全是Connection refused to master。Cluster 模式Driver 进程由 Master 分配到某个 Worker 节点上启动。命令加--deploy-mode cluster即可。此时 Driver 和 Executor 在同一集群内网络稳定且 Master 会监控 Driver 心跳异常时可尝试重启。但代价是调试困难Driver 日志不在本地得去对应 Worker 的work/目录下找路径类似/opt/spark/work/app-20231015142233-0001/0/。强烈建议生产环境一律用 Cluster 模式Client 模式仅限开发测试。提交后Master 的日志logs/spark-root-org.apache.spark.deploy.master.Master-1-master.log会记录关键事件INFO Master: Registering worker 192.168.1.11:7078 with 4 cores, 8.0 GB RAM INFO Master: Received request to launch application app-20231015142233-0001 INFO Master: Launching executor app-20231015142233-0001/0 on worker worker-20231015142233-0001看到Launching executor表示资源已分配接着去对应 Worker 日志查 Executor 启动详情。3.3 Standalone 模式下的内存模型与 OOM 排查实战Standalone 的内存模型是“三层嵌套”Worker JVM 堆内存 → Executor JVM 堆内存 → Executor 内部堆外内存。这导致 OOM 故障常发生在“看不见”的地方。Worker 层 OOM表现为 Worker 进程崩溃日志里java.lang.OutOfMemoryError: Java heap space。原因通常是SPARK_WORKER_MEMORY设太小或单 Worker 上启动了过多 Executor。解决方案降低spark.executor.instances或增加 Worker 内存。Executor 层 OOM最常见日志里java.lang.OutOfMemoryError: GC overhead limit exceeded。这通常不是堆内存不足而是Shuffle 数据量过大堆外内存Netty buffer爆了。Spark 3.x 默认spark.shuffle.io.numConnectionsPerPeer8每个连接缓冲区默认 32KB8 个连接就是 256KB1000 个 Shuffle 分区就占用 256MB 堆外内存。实测发现当spark.executor.memory4g时堆外内存常占 1.2g若SPARK_WORKER_MEMORY没留足余量Worker 就先崩了。排查工具链jstat -gc worker-pid查 Worker JVM 堆使用率jstack worker-pid | grep RUNNABLE看线程是否卡在 Netty writelsof -p worker-pid | wc -l查打开文件数超 65535 常因 Shuffle 连接数过多。实操心得在conf/spark-defaults.conf中强制设置spark.shuffle.io.preferDirectBufsfalse让 Netty 用堆内内存替代堆外虽慢 5%但彻底规避堆外 OOM。这是我们在某物流轨迹分析项目中验证有效的“保命参数”。4. Yarn 模式深度解析从资源申请到 Container 生命周期管控4.1 Yarn 资源模型与 Spark 参数映射关系Yarn 的资源单位是Container每个 Container 有 CPU Core 数和 Memory MB 两个维度。Spark 提交时必须把自身的 Executor 需求翻译成 Yarn 的 Container 请求这个翻译过程充满“隐性损耗”。关键映射关系如下Spark 参数Yarn 映射逻辑实操要点--num-executors决定申请多少个 Container但实际数量受 Yarn 队列yarn.scheduler.capacity.root.default.maximum-capacity限制设 100 可能只批 32--executor-cores每个 Container 的 vCore 数必须 ≤yarn.nodemanager.resource.cpu-vcores否则 Container 无法启动--executor-memoryContainer 内存 --executor-memoryspark.yarn.executor.memoryOverhead默认 0.1 * executor-memory最小 384MB若--executor-memory4g则 Container 内存 4g 384m 4.375gYarn 会向上取整到 4500m--driver-memoryDriver Container 内存 --driver-memoryspark.yarn.driver.memoryOverhead同理Driver 也受 Yarn 内存限制最易错的点是spark.yarn.executor.memoryOverhead。很多团队只调--executor-memory却忘了 Overhead 是硬开销。Yarn 分配 Container 时会检查yarn.nodemanager.resource.memory-mb如 64g是否 ≥ 所有 Container 内存之和。假设你设--num-executors50、--executor-memory4g那么 Container 内存需求 50 × 4500m 225000m ≈ 220g远超单节点 64gYarn 就只能在多个节点分散调度导致启动慢、网络传输多。4.2 Yarn 模式下的提交命令与 ApplicationMaster 行为Yarn 模式提交命令比 Standalone 复杂但每一步都有明确目的spark-submit \ --master yarn \ --deploy-mode cluster \ # Driver 必须在 Yarn Container 内 --queue default \ # 指定 Yarn 队列权限和配额由此控制 --num-executors 20 \ --executor-cores 4 \ --executor-memory 4g \ --driver-memory 2g \ --conf spark.yarn.am.memory2g \ # AM Container 内存 --conf spark.yarn.am.cores2 \ # AM vCore 数 --conf spark.yarn.executor.memoryOverhead1024 \ # 显式设 Overhead 为 1g --conf spark.sql.adaptive.enabledtrue \ # 开启自适应查询优化 --class com.example.BatchJob \ hdfs://namenode:8020/jars/batch-job.jar提交后Yarn 的行为链路是Client 向 ResourceManagerRM提交 ApplicationRM 返回 Application ID如application_1697385600000_0001RM 在某个 NodeManagerNM上启动 ApplicationMasterAMContainerAM 启动后向 RM 申请 20 个 Container每个 4g1g OverheadRM 分配 Container 后AM 通知对应 NM 启动 ExecutorExecutor 启动后反向注册到 AMAM 开始调度任务。AM 的日志yarn logs -applicationId application_1697385600000_0001 -am ALL是排障金矿。常见错误Failed to request executors from YARNYarn 队列资源不足用yarn queue -status default查剩余资源Container exited with a non-zero exit code 143Container 被 Yarn 杀掉通常是内存超限yarn.nodemanager.vmem-pmem-ratio默认 2.1即物理内存 4g 时虚拟内存最多 8.4g超了就 killAM container is launched, waiting for AM container to Register with RMAM 启动慢调大spark.yarn.am.waitTime默认 100s。4.3 Yarn 模式下的日志聚合与监控集成Yarn 模式最大的运维优势是日志统一管理。默认开启yarn.log-aggregation-enabletrue后所有 Container 日志包括 AM、Executor会自动上传到 HDFS 的/app-logs/user/logs/目录。查看日志不再需要 SSH 到各节点# 查看整个应用日志含 AM 和所有 Executor yarn logs -applicationId application_1697385600000_0001 # 只看 Driver即 AM日志 yarn logs -applicationId application_1697385600000_0001 -am ALL # 查看某个 Executor 日志ID 从 Web UI 复制 yarn logs -applicationId application_1697385600000_0001 -containerId container_1697385600000_0001_01_000002监控层面Yarn 提供 REST APIhttp://rm-host:8088/ws/v1/cluster/apps/application_1697385600000_0001获取应用状态、资源使用、运行时长http://rm-host:8088/ws/v1/cluster/metrics集群总资源、活跃节点数http://rm-host:8088/ws/v1/cluster/scheduler队列资源分配详情。我们用 Prometheus 抓取这些 APIGrafana 做看板关键指标包括appsRunning运行中应用数突增说明有任务堆积allocatedMB / availableMB队列资源使用率80% 预警containersAllocated已分配 Container 数对比num-executors看是否满足。注意Yarn 日志聚合有延迟通常 5~10 分钟才上传完成。紧急排障时先用yarn top看实时 Container 状态再查 HDFS 日志。5. Standalone 与 Yarn 模式对比实战一次电商实时推荐系统的迁移手记5.1 迁移背景与目标设定某电商公司的实时推荐系统原先运行在 8 节点 Standalone 集群上承载用户点击流实时处理Kafka → Spark Streaming → Redis。随着大促流量增长问题频发单日峰值 QPS 从 5k 涨到 50kStandalone Master 频繁 Full GCWeb UI 卡死新增的离线特征计算Spark SQL和实时任务争抢 Worker 资源导致推荐延迟从 200ms 涨到 2s安全部门要求接入公司统一 Kerberos 认证Standalone 无法满足。迁移目标明确稳定性P99 延迟 ≤ 500ms可用性 ≥ 99.95%资源隔离实时任务队列realtime和离线任务队列batch物理隔离互不影响安全合规所有 Spark 任务通过 Kerberos 认证访问 HDFS/HBase。5.2 迁移步骤与关键配置调整Step 1Yarn 集群准备在现有 Hadoop 集群12 节点上启用 CapacityScheduler创建两个队列!-- capacity-scheduler.xml -- property nameyarn.scheduler.capacity.root.queues/name valuedefault,realtime,batch/value /property property nameyarn.scheduler.capacity.root.realtime.capacity/name value40/value !-- 40% 总资源 -- /property property nameyarn.scheduler.capacity.root.batch.capacity/name value30/value /property配置 Kerberoscore-site.xml加hadoop.security.authenticationkerberosyarn-site.xml加yarn.resourcemanager.principalyarn/_HOSTEXAMPLE.COM。Step 2Spark 应用改造修改提交脚本去掉--master spark://...改为--master yarn --queue realtime关键参数重算原 Standalone 用--executor-memory 6gYarn 下设--executor-memory 5g --conf spark.yarn.executor.memoryOverhead1024确保 Container 内存 5g 1g 6g与原 Worker 内存一致启用动态资源分配--conf spark.dynamicAllocation.enabledtrue --conf spark.dynamicAllocation.minExecutors5 --conf spark.dynamicAllocation.maxExecutors50应对流量峰谷。Step 3数据源适配KafkaStandalone 用--packages org.apache.spark:spark-sql_2.12:3.4.2Yarn 下需把 Kafka JAR 放到 HDFS用--jars hdfs://namenode:8020/jars/kafka-clients-3.3.1.jarRedis原用redis.clients:jedisYarn 下同样需上传 JAR并在--driver-class-path中指定。5.3 迁移后效果与性能对比指标Standalone 模式Yarn 模式提升P99 延迟1850ms420ms↓ 77%资源利用率CPU75%波动大62%平稳更均衡故障恢复时间平均 8 分钟需手动重启 Master平均 45 秒AM 自动重试↑ 90%安全审计通过率0%无 Kerberos100%Yarn 统一认证从 0 到 1运维效率每周 12 小时排障每周 3 小时日志聚合API 监控↑ 75%最直观的变化是大促当天实时队列realtime资源使用率峰值 92%而离线队列batch仍保持 28%完全没受干扰。Standalone 时代那种“一跑报表推荐就卡”的窘境彻底消失。6. 常见问题与独家避坑指南来自 12 个生产集群的血泪总结6.1 Standalone 模式高频问题速查问题现象根本原因解决方案我的实操备注Worker 启动后立即退出日志无报错SPARK_WORKER_MEMORY设为8g但系统物理内存仅 16gOS 无法分配足够内存给 JVM用free -h查可用内存SPARK_WORKER_MEMORY设为物理内存的 50%某次在 32g 内存服务器上设24gWorker 死活起不来dmesg显示Out of memory: Kill processWeb UI 显示 Worker 状态为 UNHEALTHYWorker 心跳超时默认 60 秒网络延迟或 GC 导致心跳未及时发送调大spark.worker.timeout如 120s和spark.worker.heartbeatInterval如 10s心跳间隔不能大于超时时间否则永远 unhealthyExecutor 启动失败日志Failed to create directorySPARK_WORKER_DIR默认/tmp磁盘满或权限不足在spark-env.sh中设export SPARK_WORKER_DIR/data/spark-worker并chown spark:spark /data/spark-worker/tmp是很多系统的 tmpfs 内存盘大小固定极易满6.2 Yarn 模式高频问题速查问题现象根本原因解决方案我的实操备注Application failed 2 times due to AM ContainerAM Container 内存不足spark.yarn.am.memory默认 1g但 AM 需加载大量 Spark 类设--conf spark.yarn.am.memory2g --conf spark.yarn.am.cores2AM 内存和 cores 不参与计算但影响启动成功率宁大勿小Container killed by YARN for exceeding memory limitsContainer 物理内存超限yarn.nodemanager.vmem-pmem-ratio2.1导致虚拟内存被杀方案一调大yarn.nodemanager.vmem-pmem-ratio4.0方案二减小--executor-memory我们选方案二因为调 ratio 会掩盖真实内存问题NoClassDefFoundError: org/apache/hadoop/fs/FileSystemSpark 未正确加载 Hadoop 依赖HADOOP_CONF_DIR未设或指向错误在spark-env.sh中加export HADOOP_CONF_DIR/etc/hadoop/conf并确保该目录有core-site.xmlhdfs-site.xml这是 Yarn 模式最经典的 CLASSPATH 问题90% 的初学者栽在这里6.3 混合部署终极建议不要在同一个集群上混用两种模式有团队问“能不能让部分任务走 Standalone部分走 Yarn”答案是绝对不行。原因有三资源视图割裂Standalone 的 Worker 和 Yarn 的 NodeManager 都在争抢同一台机器的 CPU/Memory没有全局调度器协调必然导致资源浪费和争抢运维复杂度爆炸要同时维护两套监控、两套日志、两套安全策略人力成本翻倍故障定位地狱一个任务慢你得先判断它跑在哪套模式下再查对应日志效率极低。我们的实践是新项目一律上 Yarn老 Standalone 集群逐步下线。迁移时用 Spark 的--files参数把 Standalone 的spark-defaults.conf挂到 Yarn 任务中平滑过渡。某客户用了 18 个月完成全量迁移零业务中断。最后分享一个小技巧在spark-submit命令前加env SPARK_PRINT_LAUNCH_COMMANDtrue它会打印出 Spark 实际执行的 Java 命令包括所有 JVM 参数和 Classpath。这是排查“为什么参数没生效”的终极武器——我靠它揪出过 3 次spark-defaults.conf路径写错的低级失误。