news 2026/10/22 14:25:49

Spark 3.2 Standalone分布式集群搭建实战:从环境配置到资源调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spark 3.2 Standalone分布式集群搭建实战:从环境配置到资源调优

最近几周连续有朋友问同一个问题:Spark源码和原理看了不少,一到自己动手搭集群就卡壳,尤其是"该选哪种部署方式"这一步。我给的回答一直很简单——学习阶段别急着上YARN或K8S,先用Spark Standalone分布式搭建把整个资源调度过程跑通,性价比最高。这篇教程(系列第九篇)就是基于Spark 3.2在CentOS7上完整搭建Standalone分布式集群的实战记录,踩过的坑、每个配置项背后的原因都会写清楚,适合正准备从单机local模式跨到分布式环境的读者,也适合那些搭完集群但总报各种诡异问题、想一次性弄明白的人。

1. 为什么我建议用Standalone模式做Spark 3.2入门集群

1.1 三种部署模式的第一层取舍

先纠正一个常见的认知误区:很多人觉得学Spark就应该直接奔着生产环境的标准去,一上来就搭YARN甚至K8S,结果环境还没跑通,先被资源管理器本身的部署给劝退了。实际学习阶段的诉求和线上完全不一样——你需要的是最快速度看到一个Task怎么被调度到多台机器上执行,而不是先搞懂数百项的容器编排配置。

Spark本身支持四种部署方式:local、Standalone、YARN、K8S/Mesos。local模式只能单机跑,理解不了"分布式"到底分布在哪里;YARN需要额外维护HDFS和ResourceManager,一套Hadoop环境搭下来少说两个小时;K8S就更重了,里面牵扯的资源模型和Spark自身完全隔离。而Standalone模式最大的优势是Spark自带完整的Master-Worker调度体系,不需要任何外部组件,解压配置就能起集群。

从原理层面讲,Standalone模式下的Master节点负责接收作业请求、给Worker分配Executor,Worker节点负责启动Executor进程并汇报心跳。这套调度模型跟YARN其实很像,只是实现得朴素很多——但对你理解"Driver请求资源、Executor执行任务、Task并行度"这些核心概念来说,朴素反而更友好,因为你能直接看到每一步发生了什么。

1.2 这次搭建的集群规划与角色分配

我这次准备了三台CentOS7虚拟机,节点命名统一用node101、node102、node103,内存各4G、双核CPU。三台是底线,一台Master加两台Worker,分布式特征才能完整呈现——比如一台Worker挂了作业还能不能继续跑、Executor在多个节点上如何横向分布,这些场景两台Worker才能玩得开。

角色分配如下:

节点角色核心职责
node101Master + Worker资源调度中枢,同时参与计算
node102Worker启动Executor执行任务
node103Worker启动Executor执行任务

为什么不单独用一台纯Master?原因很简单:三台机器每台只有4G内存,如果Master单独占一台,剩下的两台Worker各4G内存,跑几个像样的测试作业就吃紧了。我自己测试下来,让Master节点兼任Worker,只要SPARK_WORKER_INSTANCES设置合理(后面会讲),对调度稳定性几乎没有影响,但能多出整整一台机器的计算资源。当然如果你是8G内存以上的机器,纯Master节点分工更干净,排查问题时角色边界也更清晰。

2. 环境准备阶段三个绕不过的坑

2.1 JDK版本与Spark 3.2的兼容性

这一步看着简单,实际是整篇搭建过程中最容易埋雷的地方。Spark 3.2官方支持JDK 8和JDK 11,听起来很宽松,但当你用yum直接装OpenJDK的默认版本时,CentOS7默认源可能会给你装上openjdk 1.8.0的某个老版本。这本身没问题,问题出在版本号细节:Spark 3.2需要JDK 8u201及以上版本,否则部分Java类在编译或运行时会出现奇怪的UnsupportedClassVersionError。

我的建议是一律手动安装固定版本。在三台机器上分别执行:

yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel

装完立刻验证版本,确认输出中不是过旧的小版本号:

java -version

然后是JAVA_HOME环境变量。这一步容易被忽略的是:OpenJDK通过yum安装后路径通常是/usr/lib/jvm/java-1.8.0-openjdk,但这个路径是个软链接,实际指向可能随版本变化。最稳妥的做法是先readlink -f拿到真实路径,再写进/etc/profile:

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk export PATH=$JAVA_HOME/bin:$PATH

配完执行source /etc/profile,三台机器都要配。很多Worker节点启动失败,最后查来查去就是JAVA_HOME没生效——Spark的start脚本检测不到Java,会直接报JAVA_HOME is not set,但报错位置非常隐蔽,经常被淹没在启动日志里。

2.2 SSH免密登录与主机名映射

Spark的start-all.sh脚本会通过SSH远程登录到workers文件里列出的节点去逐个启动Worker,如果每一次登录都要输密码,脚本会直接卡死在半路。所以Master到所有Worker节点(包括自己)的SSH免密登录是整个集群启动的大前提。

配置过程分三步,我在每个节点上把~/.ssh/id_rsa生成好:

ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa

然后在node101上把公钥分发到所有节点,包括node101自身:

ssh-copy-id node101 ssh-copy-id node102 ssh-copy-id node103

这里有一个我当年栽过的跟头:如果你规划Master也承担Worker角色,一定记得ssh-copy-id node101也要执行。因为start-all.sh在启动本地Master之后,还会再SSH连一次本机去启动Worker,如果本机免密没配好,你会在启动日志里看到Master已经起来了但Worker全部失败。

然后是/etc/hosts配置,三台机器上都要写同样的内容,否则Spark节点之间通过主机名通信会解析失败。我这里用内网示例IP做演示,你替换成自己的实际IP即可:

192.168.1.101 node101 192.168.1.102 node102 192.168.1.103 node103

配完hosts之后需要重新登录一次SSH会话,否则当前终端的hostname解析缓存不会刷新。

2.3 防火墙与SELinux的取舍

这节内容在真实场景里争议很大,但对我来说结论很明确:如果是自己测试的虚拟机环境,直接systemctl stop firewalld并禁止开机自启,能省掉后面一连串的端口排查时间。

systemctl stop firewalld systemctl disable firewalld

SELinux同理,测试环境建议先setenforce 0临时关闭。我之前遇到过一种极其隐蔽的情况:防火墙开着但Spark的8080端口已经手动放行,Web UI也能正常访问,结果作业提交一直卡在Waiting for resources,查了半天才发现是SELinux拦截了Worker和Master之间的7077端口通信,日志里却只显示连接超时。说实话这类权限拦截在真实线上环境应该仔细分析再决定,但学习阶段完全没必要在这一环浪费时间,先关掉,等集群跑顺了再回头研究加固也不迟。

3. 解压配置:Spark 3.2与老版本的一个关键差异

3.1 目录结构与spark-env.sh逐项拆解

下载spark-3.2.1-bin-hadoop3.2版本的安装包,解压到指定目录后,建议第一时间把目录改名成简短路径,不然$SPARK_HOME环境变量要写很长一串:

tar -zxvf spark-3.2.1-bin-hadoop3.2.tgz mv spark-3.2.1-bin-hadoop3.2 /opt/spark

然后配置SPARK_HOME并加入PATH,三台机器同步:

export SPARK_HOME=/opt/spark export PATH=$SPARK_HOME/bin:$SPARK_HOME/sbin:$PATH

紧接着是全文最关键的一个文件:$SPARK_HOME/conf/spark-env.sh。默认只有spark-env.sh.template模板,先复制一份再改:

cp $SPARK_HOME/conf/spark-env.sh.template $SPARK_HOME/conf/spark-env.sh

我最终用的配置如下,每项的用途逐个说明:

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk export SPARK_MASTER_HOST=node101 export SPARK_MASTER_PORT=7077 export SPARK_WORKER_CORES=2 export SPARK_WORKER_MEMORY=3g export SPARK_WORKER_INSTANCES=1
  • SPARK_MASTER_HOST:Master绑定的主机名,必须写能被所有节点解析到的主机名或IP,写成域名或localhost会导致Worker无法注册。
  • SPARK_MASTER_PORT:Master与Worker通信的RPC端口,默认7077,一般不用改。
  • SPARK_WORKER_CORES:每个Worker最多可分配给Executor的CPU核数。
  • SPARK_WORKER_MEMORY:每个Worker可供Spark作业使用的总内存,注意是"可供Spark使用",不是机器物理内存总额。后面讲资源调优时这里是个大坑。
  • SPARK_WORKER_INSTANCES:每台节点上启动的Worker进程实例数量。我固定设成1,避免一个节点上多个Worker进程互相抢资源。

这里多提醒一句:SPARK_WORKER_MEMORY不要贪。我测试机上物理内存4G,给Worker分配3g已经算激进了,因为你还要给操作系统、Master进程、后续提交作业的当前进程留余量。如果设成4g,Worker启动时大概率正常,第一个作业提交上来就内存耗尽,整个节点卡死。

3.2 workers文件:别再用Slaves了

这是Spark 3.2和老版本最明显的差异之一:老版本用来声明Worker节点清单的文件叫slaves,但从Spark 3.0起这个文件已经改名为workers,老教程里让你改slaves文件的操作在3.2版本下完全不生效。我当时刚从老版本的习惯迁移过来,愣是照着一个旧文配了配置,结果start-all.sh只起了Master,一个Worker都没起来。

正确操作是把$SPARK_HOME/conf/workers.template复制为workers,然后在里面逐行写节点名:

node101 node102 node103

值得注意的一点是:通常教程会建议Master节点不要写入workers文件,但我这里由于Master作为Worker参与计算,所以三台节点都在清单里。如果你的规划是纯Master节点,那就只在workers里写Worker节点清单。

3.3 配置文件同步与目录权限

配置改完之后,最省事的方式是把整个Spark目录同步到其他节点,而不是一台一台手动去改:

scp -r /opt/spark root@node102:/opt/ scp -r /opt/spark root@node103:/opt/

这里有一个常见问题:解压后的Spark目录默认属主是当前用户,如果你在node101上用root操作,但其他节点的用户权限不一致,会导致启动脚本执行时Permission Denied。建议在三台机器上统一指定一个运维用户(或者统一用root测试),然后:

chown -R root:root /opt/spark

配置文件分发完毕后,最好在每台节点上检查一下workers文件有没有被意外覆盖。

4. 启动集群:确认的不只是进程还有调度

4.1 启动顺序:先Master再Worker

很多人直接跑sbin/start-all.sh一把梭,我相信你在很多教程里也看到过这个写法。但这个脚本做的事情是:先在当前节点启动Master,再根据workers文件去挨个启动Worker。它默认认为你的workers文件已经配好、SSH免密已经生效,如果这两点没满足,报错会非常迷惑。

我的建议是分步启动,方便定位问题。先在node101上启动Master:

sbin/start-master.sh

启动成功后用jps应该能看到Master进程。然后再次在node101上启动所有Worker:

sbin/start-workers.sh

看到每个节点上都出现了Worker进程,说明注册成功。这里我特意用start-workers.sh而不是start-all.sh,逻辑上更清晰:把启动Master和启动Worker两个步骤解耦,哪一步出了问题,一眼就能定位到是Master的配置问题还是Worker节点的通信问题。

4.2 Web UI与日志:验证集群真正健康

进程起来不代表集群真的健康。Spark Standalone提供两个Web UI页面,是判断集群状态最直观的手段:

  • Master UI:http://node101:8080
  • Worker UI:http://node102:8081(每个Worker节点独立端口)

打开Master UI后,重点看两个信息:第一,Workers表格里有没有出现node101、node102、node103三行记录;第二,每行记录的Alive状态是否为1。如果节点数量对不上,或者状态反复变化,说明网络通信链路有问题,需要回头检查hosts和防火墙。

日志的查看位置也值得记一下:$SPARK_HOME/logs/目录下,Master和Worker的日志文件按日期滚动生成。启动Worker失败时的具体原因,比如"Unable to connect to Master"、"Executor registration timed out",基本都能在这个目录下找到。

4.3 提交第一个正式作业并追踪任务

集群起来后,用Spark自带的examples包里的Pi计算程序做一次完整的端到端验证:

$SPARK_HOME/bin/spark-submit \ --master spark://node101:7077 \ --deploy-mode client \ --executor-memory 2g \ --executor-cores 2 \ --total-executor-cores 4 \ --class org.apache.spark.examples.SparkPi \ $SPARK_HOME/examples/jars/spark-examples_2.12-3.2.1.jar 100

执行成功后刷新Master UI,应该能在Running Applications或Completed Applications区域看到这个作业的记录。点进去还能看到每个Executor分布在哪个Worker节点上、每个Task的执行时间。这个界面值得多研究一下,它能帮你直观理解"两个Worker节点各分配了一个Executor,每个Executor里并行跑着两个Task"这件事,比任何原理图都有说服力。

5. Standalone模式下的资源调优与真实踩坑

5.1 Worker内存与Executor内存的关系

我先用一个真实场景来说:刚开始我把SPARK_WORKER_MEMORY设成3g,然后用--executor-memory 3g提交作业,结果集群调度作业时直接报错。原因其实很直接——每个Worker的3g内存并不是一个整体,Executor在这个上限里面申请资源,但并不能一次性把所有内存都占满。

Standalone模式下,每个Worker节点向Master上报自己能提供的内存资源,然后Master把这个资源池按Executor粒度切分。Executor内存由--executor-memory决定,但它只是Executor的堆内存,实际操作中Spark还会在堆外分配一些内存,所以一个Executor实际占用的物理内存会略高于你指定的值。当Worker可分配的总量不足以容纳你申请的所有Executor时,作业就永远处于Waiting for resources状态。

我在三节点测试环境里总结出一个安全配比:Worker总内存3g,提交作业时--executor-memory设置在2g左右,--executor-cores不超过2,这样每个Worker能容纳一个Executor,整体集群还能留出一个备用槽位。这套配比的关键是:Worker的可用内存必须大于Executors申请总量的峰值,否则作业要么排队等到天荒地老,要么直接内存溢出。

5.2 spark-submit提交参数的正确姿势

节省篇幅,我直接给出一套跨节点跑分布式作业的标准姿势。注意两个关键词,一个是资源相关参数,另一个是部署模式:

  • --executor-memory:单个Executor的堆内存
  • --executor-cores:单个Executor使用的CPU核数
  • --total-executor-cores:整个作业最多占用的总CPU核数
  • --deploy-mode client:Driver运行在提交作业的客户端

我见过很多人在这里犯混淆:以为--executor-memory设置得越大越好,或者以为--total-executor-cores是"每台机器的核数",导致任务数量远多于实际可供分配的核数,大量Task排队。正确的理解是:total-executor-cores是全局上限,Master会在所有Worker节点上统筹资源来满足你申请的需求。

还有一个细节:因为集群里每台Worker报了3g可用内存,如果你用--executor-memory 2g --total-executor-cores 4这种组合,Master会在两台Worker上各启动一个Executor,每个Executor占2g核2,资源刚好分配干净。但如果再叠加--executor-memory 2g --total-executor-cores 6,按照每个Worker当前的空闲核数,Master最多只能按4核调度,多出来的2核会一直等待——这不是死机,而是正常的资源供需关系在起作用。

5.3 一次Executor内存溢出的复盘

有一次提交一个稍重的测试作业,我直接把--executor-memory提到了3g,想着既然SPARK_WORKER_MEMORY给了3g就能用满。跑起来不到一分钟,节点上的物理内存直接被打满,紧接着Executor进程被操作系统OOM Killer干掉。日志里能看到一行含糊的Killed,而不是Java堆内存溢出。

复盘后问题有三层:第一层,Worker总内存3g与Executor申请3g相等,看上去够用,但Spark Executor除了堆内存还有约300M的堆外开销;第二层,容器进程不是JVM一个,还有代码缓存、线程栈等额外内存;第三层,机器物理内存4g中,Worker占3g后留给操作系统和Master的只剩1g,这显然不够。所以Executor内存填满Worker上限是极端危险操作,留出至少20%的余量才是正确做法。之后我把SPARK_WORKER_MEMORY改回3g,提交参数用--executor-memory 2g,一切恢复正常。

6. 排障手册:Standalone集群最常见三个故障

6.1 Worker进程反复消失:先看日志再动配置

最典型的现象是:start-workers.sh执行时Worker进程短暂出现,过几秒又从jps里消失了。绝大多数人第一反应是去改spark-env.sh里的一大堆参数,但我踩过这个坑之后养成了一个习惯——任何进程存活性问题,先打开日志确认原因,再决定动哪里。

Worker日志路径固定,命令是:

tail -100 $SPARK_HOME/logs/spark-root-worker-*.out

日志里如果出现Failed to connect to master,大概率是Master地址问题,检查SPARK_MASTER_HOST是否在Worker节点的/etc/hosts里能解析;如果出现Master has no available resources,则是内存分配不够;如果出现Lost connection to master,很可能是网络不稳定或防火墙中断了定时心跳。每次看到有人从配置文件开始排查,我内心都会替那个日志文件觉得委屈——它里面写得清清楚楚。

6.2 Web UI打不开 / 任务卡住:端口与网络的排查顺序

Web UI打不开这个现象,我见过三种不同原因:本地能开远端开不了、所有节点都开不了、Master能开Worker开不了。排查顺序建议是固定的:

  1. 用ss -tlnp | grep 8080确认Master进程确实在监听端口
  2. 用telnet node101 8080测试端口连通性
  3. 检查防火墙是否拦截了8080/8081/7077端口

任务卡住不执行的排查则是另一套逻辑:打开Master UI先看集群资源是不是已经用满,再看作业页面的Executor列表。如果Executor一直处于Launching状态,十有八九是Worker节点资源不足或者网络通信有延迟;如果Executor正常但Task全在Pending,问题通常在CPU核数分配上,回到第5节讲的参数关系去调整。

6.3 master URL细节:提交作业时最常见的拼写坑

spark-submit --master spark://node101:7077 ...

这里的spark://协议前缀是必须写的,省略或写错都会让客户端认不出Master地址。另外--deploy-mode默认是client模式,这个模式要求客户端机器能通过网络访问到所有Executor节点——如果你在一个和集群网络隔离的本机提交作业,即使URL写对,Executor反向连接Driver时也可能失败,表现为作业提交后Task全失败。

一个容易被忽视的细节:node101这个主机名必须在客户端机器上也解析到正确IP,否则客户端连不上。跨网络提交时直接写IP更稳妥,比如spark://192.168.1.101:7077。这个坑在单机测试时根本暴露不出来,一旦脱离集群内部环境,立刻就会撞上。


最后说点个人体会。Standalone模式虽然最终生产环境可能不会直接用,但它给我建立的资源调度直觉,在后来排查YARN上各种分配问题时帮了大忙——理解了Executor和Worker的边界、理解了客户端模式的通信路径,再看别的资源管理器,只是换了一层壳而已。如果你现在也在学习Spark的路上,我建议配置一次这个集群,然后跑到Web UI里反复观察Task的分布,这比直接背十遍调度源码都有用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 14:22:56

毫米波混合预编码信道估计:超分辨率MATLAB资源包实战

简介:这份资源聚焦毫米波大规模MIMO系统中的信道估计与混合预编码,面向无线通信、5G物理层方向的研究生与工程技术人员,帮助解决毫米波信道多径复杂、传统LS/MMSE估计精度不足的问题。压缩包共5个文件,以4个m脚本和1个txt说明为主…

作者头像 李华
网站建设 2026/10/22 14:22:51

基于QT与MySQL的点餐系统开发实践:从环境搭建到避坑指南

简介:基于Qt与MySQL开发的点餐系统完整源码包,面向C/Qt学习者及需要完成课程设计、毕业设计的高校学生。项目按实际工程结构组织,包含登录界面、主窗口、数据库管理模块,以及菜品图片素材、样式表和Qt资源文件,下载后可…

作者头像 李华
网站建设 2026/10/22 14:21:52

基于扩散模型的年龄迁移:从少年到老年的连续可控人脸生成

1. 先把任务拆清楚:年龄"迁移"到底在改什么 最近我拿了一批跨越不同年龄段的人脸照片做实验,目标是让同一个人既能回到少年时代,也能一键推到七八十岁,而且要"看起来像同一个人",不是换头&#xf…

作者头像 李华
网站建设 2026/10/22 14:21:32

基于lycium_plusplus将x264移植到鸿蒙PC的完整实践

移植一个C库到全新的操作系统,最怕的不是代码本身,而是构建系统不认你的环境。x264这种老牌编码库,configure脚本里面写死了对Linux、macOS、Windows的判断逻辑,到了鸿蒙PC上直接懵掉。上个月我花了两天时间,用官方最新…

作者头像 李华
网站建设 2026/10/22 14:21:30

Linux iwd 连 WPA3-SAE 反复掉线:SA Query timed out 降级 WPA2 解决

现象:无线每隔几分钟就被踢一次 一台跑 Arch Linux 的笔记本,用 iwd 作为无线后端(不是 wpa_supplicant,也不是 NetworkManager),通过 Wi-Fi 把麦克风音频以 UDP 推到局域网另一台机器。表现是:…

作者头像 李华
网站建设 2026/10/22 14:20:23

Spring Boot 3 + GraalVM AOT在ARM工控机上跑YOLO的实践

去年年中,项目组要把一批边缘目标检测任务从云端算力盒子迁到现场工控机上。设备用的是飞腾系ARM处理器,系统是Linux,原来每台设备都要按月租GPU推理服务,量一多成本就压不住。迁移时我们没有走最常见的Python推理栈,而…

作者头像 李华