Spark Submit Yarn Client与Cluster模式差异及启动失败问题排查
关于YARN Client/Cluster模式的问题解答
一、Cluster模式作业无法启动的非SparkSession原因
你提到Spark 2.0+推荐用SparkSession,但这绝对不是cluster模式启动失败的唯一原因,结合EMR的场景,我帮你梳理几个常见的坑:
- 文件路径错误:Client模式下驱动跑在EMR主节点,你写的
"filepath"如果是主节点本地路径(比如/home/hadoop/test.txt)能正常读取,但Cluster模式下驱动会被调度到某个核心节点的Container里,这个本地路径在核心节点根本不存在!必须改成HDFS路径,比如hdfs:///user/hadoop/test.txt或者直接/user/hadoop/test.txt(Spark会自动识别HDFS)。 - 权限不匹配:Client模式下作业以你提交时的用户身份运行(比如主节点的hadoop用户),但Cluster模式下YARN会以提交用户的身份在核心节点启动驱动,可能这个用户没有访问目标HDFS路径、YARN队列的权限,或者无法读取某些配置文件。
- 依赖缺失:如果你的代码依赖第三方JAR包,Client模式下可能这些JAR在主节点本地能被加载,但Cluster模式下驱动Container里没有这些依赖,必须通过
--jars、--packages或者EMR的bootstrap动作提前把依赖放到集群节点上。 - YARN资源配置不足:EMR的YARN默认配置可能给驱动Container分配的内存太小,导致JVM启动失败。你可以通过
--driver-memory参数增大驱动内存,比如spark-submit --master yarn --deploy-mode cluster --driver-memory 2g ...,同时检查YARN队列的资源配额是否足够分配驱动Container。
二、Client与Cluster模式的核心差异
这两种模式的本质区别在于驱动程序的运行位置,衍生出一系列差异:
- 驱动位置:
- Client模式:驱动程序直接运行在你提交作业的机器(EMR主节点)上,和YARN集群是远程交互。
- Cluster模式:驱动程序被YARN调度到集群内某个核心节点的Container中,完全在集群内部运行。
- 日志获取:
- Client模式:驱动日志直接输出在你的提交控制台,Executor日志需要去YARN日志服务器或者EMR的S3日志目录查看。
- Cluster模式:驱动和Executor的日志都存在YARN的日志系统里,需要用命令
yarn logs -applicationId <你的AppID>查看。
- 容错性:
- Client模式:如果EMR主节点挂了或者你断开了提交终端,驱动进程会终止,整个作业直接失败。
- Cluster模式:驱动在集群内部运行,就算你断开提交终端,作业依然会继续执行,直到完成或出错。
- 网络通信:
- Client模式:驱动需要和所有Executor建立跨节点连接,计算结果要从Executor传输到主节点的驱动,额外的网络延迟不可避免。
- Cluster模式:驱动和Executor都在集群内网,通信延迟极低,数据传输效率更高。
三、为什么Client模式执行速度更慢?
主要是三个核心因素导致的:
- 额外网络开销:Client模式下,Executor的计算结果要跨节点传输到主节点的驱动,尤其是涉及大量Shuffle操作或者需要返回大结果集的作业,网络传输会成为瓶颈。而Cluster模式下驱动在集群内,数据传输完全走内网,速度快很多。
- 主节点资源竞争:EMR主节点本身要运行YARN ResourceManager、HDFS NameNode等核心管理服务,Client模式下驱动在主节点运行,会和这些服务竞争CPU、内存资源,导致驱动处理任务的速度变慢。Cluster模式下驱动在核心节点的专属Container里,资源是独立分配的,不会和管理服务抢资源。
- 数据本地化率低:Cluster模式下驱动在核心节点,Spark的任务调度器能更精准地把任务分配到数据所在的节点,减少数据移动;而Client模式下驱动在主节点,调度判断的精准度下降,可能导致更多任务需要跨节点拉取数据,拖慢整体速度。
代码优化建议
虽然Spark 2.0+兼容SparkContext,但还是建议改用统一的SparkSession入口,能避免一些旧API的潜在问题,调整后的代码如下:
import org.apache.spark.sql.SparkSession val spark = SparkSession.builder() .appName("sample") .getOrCreate() val sc = spark.sparkContext val lines = sc.textFile("hdfs:///your/actual/hdfs/filepath")
内容的提问来源于stack exchange,提问作者Karthik k




