三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Spark完全分布式集群搭建:从环境准备到生产级部署全流程详解

Spark完全分布式集群搭建:从环境准备到生产级部署全流程详解

1. 项目概述:从零到一构建Spark完全分布式集群

搞大数据开发,Spark是绕不开的核心框架。很多朋友在单机上跑通Spark后,下一步就是跃跃欲试,想搭建一个真正的完全分布式环境。所谓“完全分布式”,指的是Spark的各个核心组件——Master(主节点)、Worker(从节点)——都运行在不同的物理或虚拟机器上,形成一个真正意义上的集群。这不仅是学习Spark架构的必经之路,更是生产环境部署的基石。我见过不少团队,在伪分布式环境下开发测试一切顺利,一旦上真集群就各种“水土不服”,问题往往就出在最初的环境搭建和配置上。

这次,我们就来手把手、无死角地走一遍Spark完全分布式集群的安装与部署全流程。我会基于最经典的Apache Spark原生Standalone集群模式,结合我在多个实际项目中趟过的坑,把每一步的原理、操作和背后的“为什么”都讲清楚。目标很明确:让你不仅能照着步骤成功搭起来,更能理解每个配置项的意义,未来遇到问题能自己排查。我们将使用三台虚拟机来模拟这个环境,一台作为Master,两台作为Worker,操作系统选用业界最普遍的CentOS 7。整个流程会涵盖环境准备、软件安装、关键配置、集群启动、验证测试以及最重要的排错环节。

2. 环境准备与规划:奠定稳定运行的基石

在动手安装任何软件之前,充分的环境准备是成功的一半。对于分布式系统尤其如此,网络、主机、权限这些基础如果没打好,后面会麻烦不断。

2.1 集群节点规划

我们规划一个最小化的、但完全符合分布式定义的集群:

  • Master节点 (master): 1台。负责集群资源管理和任务调度。IP: 192.168.1.100
  • Worker节点 (slave1, slave2): 2台。负责执行具体的计算任务。IP: 192.168.1.101, 192.168.1.102

注意:在生产环境中,Master节点建议配置更高的可靠性和资源,甚至可以配置多个Master做高可用(HA)。但为了聚焦核心流程,我们先从基础的单Master模式开始。

2.2 系统基础环境配置

这几步是分布式集群的通用前置条件,必须逐一落实。

1. 主机名与Hosts映射每台机器需要有一个唯一的主机名,并且所有机器需要能通过主机名互相解析。首先在三台机器上分别设置主机名:

# 在master节点执行 hostnamectl set-hostname master # 在slave1节点执行 hostnamectl set-hostname slave1 # 在slave2节点执行 hostnamectl set-hostname slave2

设置后需要重新登录或执行bash使更改生效。

接着,修改所有节点的/etc/hosts文件,添加以下内容:

192.168.1.100 master 192.168.1.101 slave1 192.168.1.102 slave2

这个操作需要在三台机器上完全一致。它的作用是让系统在本地解析master这个名称时,直接指向192.168.1.100,而不是去公网DNS查询,这对于内网集群通信至关重要。

2. 关闭防火墙与SELinux防火墙和SELinux可能会阻断集群节点间的通信端口,在学习和测试环境,我们通常选择关闭它们以简化问题。在生产环境,则需要精细配置防火墙规则。

# 关闭防火墙 systemctl stop firewalld systemctl disable firewalld # 关闭SELinux(需重启生效) setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

3. 时间同步(NTP)分布式集群中,所有节点的时间必须保持基本一致,否则会导致日志时间错乱、依赖时间的任务调度出错等问题。

# 安装并启动NTP服务 yum install -y ntp systemctl start ntpd systemctl enable ntpd # 可以选择与一个公共时间服务器同步,例如: ntpdate ntp.aliyun.com

4. SSH免密登录配置这是Spark Standalone集群启动的关键。Master节点需要通过SSH远程登录到各个Worker节点去启动对应的守护进程。配置免密登录后,可以避免每次启动都需要输入密码。

  • 在Master节点生成密钥对:ssh-keygen -t rsa,一路回车。
  • 将公钥分发到所有节点(包括自己):
    ssh-copy-id master ssh-copy-id slave1 ssh-copy-id slave2
  • 测试:在Master节点执行ssh slave1,如果能直接登录而无需密码,则配置成功。

5. Java环境安装Spark运行在JVM上,必须安装Java。建议使用Oracle JDK 8或OpenJDK 8,这是与Spark各版本兼容性最广的。

# 检查是否已安装 java -version # 如果未安装,使用yum安装OpenJDK yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel # 配置环境变量(可选,但建议),编辑 /etc/profile,添加: export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.xxx export PATH=$JAVA_HOME/bin:$PATH # 使环境变量生效 source /etc/profile

3. Spark软件安装与核心配置详解

基础环境就绪后,我们开始安装和配置Spark本身。

3.1 Spark版本选择与下载

访问 Apache Spark官网下载页 。对于学习和小规模集群,选择最新的稳定版(如3.5.x)的“Pre-built for Apache Hadoop 3.3 and later”版本即可。这个版本包含了大部分常用的依赖,开箱即用。

# 在Master节点操作,选择一个目录,如 /opt/software cd /opt/software wget https://archive.apache.org/dist/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz tar -zxvf spark-3.5.0-bin-hadoop3.tgz mv spark-3.5.0-bin-hadoop3 /opt/spark

将解压后的目录重命名并移动到/opt/spark是为了路径清晰。之后,将这个目录同步到所有Worker节点。可以使用scp命令:

scp -r /opt/spark slave1:/opt/ scp -r /opt/spark slave2:/opt/

3.2 环境变量配置

在所有节点上配置Spark环境变量,方便在任何位置使用Spark命令。 编辑/etc/profile文件,在末尾添加:

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

执行source /etc/profile使配置生效。之后可以运行spark-shell --version测试是否配置成功。

3.3 关键配置文件解析与修改

Spark的核心配置集中在$SPARK_HOME/conf目录下。我们需要配置几个关键文件。

1. spark-env.sh:定义集群环境首先复制模板文件:

cd /opt/spark/conf cp spark-env.sh.template spark-env.sh

编辑spark-env.sh,添加或修改以下内容。这些配置项决定了Master和Worker进程的运行参数。

# 指定Java安装路径,如果已设JAVA_HOME环境变量,这行可省略 export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-2.el8_8.x86_64 # 指定Master节点的主机名或IP。Worker需要知道它在哪里。 export SPARK_MASTER_HOST=master # 指定Master进程的Web UI端口,默认8080,如果冲突可以修改 export SPARK_MASTER_WEBUI_PORT=8080 # 指定每个Worker节点上,可供Spark使用的CPU核心总数。 # 建议设置为物理核心数,或根据资源争用情况调整。 export SPARK_WORKER_CORES=4 # 指定每个Worker节点上,可供Spark使用的内存总量。 # 格式如 4g 表示4GB。注意:这里要预留一部分内存给操作系统和其他服务。 # 例如机器有8G内存,可以设置为 6g 或 7g。 export SPARK_WORKER_MEMORY=6g # 指定Spark守护进程(Master/Worker)的日志目录 export SPARK_LOG_DIR=/opt/spark/logs # 指定Spark工作目录,用于存储作业的临时数据、jar包等 export SPARK_WORKER_DIR=/opt/spark/work

实操心得:SPARK_WORKER_MEMORY的设置是个学问。设得太满,机器可能因内存不足而卡死;设得太少,资源利用率低。一个经验法则是预留机器总内存的20%-30%给系统和其他服务。另外,如果机器上还跑着HDFS、YARN等服务,需要进一步统筹规划。

2. workers (旧版本叫slaves):指定Worker节点这个文件告诉Master,哪些机器是Worker节点。

cp workers.template workers

编辑workers文件,删除默认的localhost,添加我们的Worker主机名,每行一个:

slave1 slave2

注意:这里必须使用主机名,且这些主机名必须能在Master节点上通过/etc/hosts或DNS正确解析到IP地址。

3. 配置文件分发将修改好的spark-env.shworkers文件从Master节点复制到所有Worker节点的相同目录下。

scp /opt/spark/conf/spark-env.sh slave1:/opt/spark/conf/ scp /opt/spark/conf/spark-env.sh slave2:/opt/spark/conf/ scp /opt/spark/conf/workers slave1:/opt/spark/conf/ scp /opt/spark/conf/workers slave2:/opt/spark/conf/

确保所有节点上/opt/spark/conf目录下的这两个文件内容完全一致。

4. 集群启动、验证与基础测试

配置完成后,激动人心的启动时刻就到了。

4.1 启动与停止集群

Spark在$SPARK_HOME/sbin目录下提供了一套集群管理脚本。

  • 启动整个集群:在Master节点上执行。
    cd /opt/spark ./sbin/start-all.sh
    这个脚本会先在本机(Master)启动Master进程,然后根据workers文件列表,通过SSH免密登录依次连接到各个Worker节点,启动Worker进程。
  • 停止整个集群:在Master节点上执行。
    ./sbin/stop-all.sh
  • 单独启动/停止Master或Worker
    ./sbin/start-master.sh ./sbin/stop-master.sh ./sbin/start-worker.sh spark://master:7077 ./sbin/stop-worker.sh
    在某些调试场景下,单独控制进程很有用。

4.2 验证集群状态

启动后,如何确认集群真的在正常运行呢?有以下几种方式:

1. 使用JPS命令查看Java进程在Master节点执行jps,应该能看到Master进程。 在Worker节点执行jps,应该能看到Worker进程。 如果看不到,说明进程启动失败,需要去查看日志。

2. 查看Web UI界面Spark提供了非常直观的Web监控界面。

  • Master Web UI:在浏览器中访问http://master:8080。这是最重要的一个界面,你会看到:
    • URL:spark://master:7077,这是应用程序连接集群时用的主URL。
    • Workers:列表显示所有已注册的Worker节点,包括它们的地址、状态、CPU核心数、内存信息。正常情况下,slave1slave2都应该显示为ALIVE状态。
    • Running Applications:当前正在运行的应用程序。
    • Completed Applications:已完成的应用历史。
  • Worker Web UI:每个Worker也有自己的UI,默认端口是8081。你可以访问http://slave1:8081查看该Worker上执行的任务详情。

3. 查看日志文件如果进程启动异常,首要检查点就是日志。日志目录由SPARK_LOG_DIR指定(我们设在了/opt/spark/logs)。查看Master的日志:

tail -f /opt/spark/logs/spark--org.apache.spark.deploy.master.Master-1-master.out

查看特定Worker的日志(在对应机器上):

tail -f /opt/spark/logs/spark--org.apache.spark.deploy.worker.Worker-1-slave1.out

日志会详细记录进程启动、注册、心跳等全过程,是排错的第一手资料。

4.3 运行一个简单的测试任务

集群跑起来了,我们得试试它能不能干活。用Spark自带的示例程序来跑一个简单的Pi计算任务。 在Master节点上,执行以下命令:

/opt/spark/bin/spark-submit \ --master spark://master:7077 \ --class org.apache.spark.examples.SparkPi \ /opt/spark/examples/jars/spark-examples_2.12-3.5.0.jar \ 100

这条命令的含义是:

  • --master spark://master:7077: 指定任务提交到我们刚搭建的Standalone集群。
  • --class ...SparkPi: 指定要运行的主类。
  • jar包路径: 指定包含该类的应用程序jar包。
  • 100: 传递给SparkPi程序的参数,代表计算Pi时使用的切片数。

执行后,你会在控制台看到大量的日志输出,最后会有一行类似Pi is roughly 3.141415...的结果。更重要的是,此时刷新Master的Web UI (http://master:8080),你应该能在“Completed Applications”里看到刚刚结束的这个应用,点击进去可以看到该应用在所有Worker上的任务执行详情、时间线、DAG图等。这证明你的集群不仅进程在跑,而且能正确分配和执行计算任务。

5. 深入配置、性能调优与生产级考量

基础集群搭建成功后,我们可以进一步探索一些高级配置和调优选项,让集群更健壮、更高效。

5.1 集群高可用(HA)配置

我们目前是单Master,如果Master节点宕机,整个集群将无法提交新任务和管理资源。生产环境必须考虑高可用。Spark Standalone支持基于ZooKeeper的Master HA。

  1. 搭建一个ZooKeeper集群(至少3个节点)。
  2. 修改所有节点的spark-env.sh:
    export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=zk1:2181,zk2:2181,zk3:2181 -Dspark.deploy.zookeeper.dir=/spark"
  3. 像平常一样启动集群。当主Master挂掉后,ZooKeeper会从备用Master(在其它节点上启动的Master进程)中选举出一个新的主Master。

5.2 资源调度与隔离

默认情况下,Spark采用FIFO(先进先出)调度器。在多个用户或团队共享集群时,这可能导致大任务独占资源。可以配置公平调度器(Fair Scheduler):

  1. $SPARK_HOME/conf下创建fairscheduler.xml文件,定义调度池和权重。
  2. spark-defaults.conf中指定:
    spark.scheduler.mode FAIR spark.scheduler.allocation.file /opt/spark/conf/fairscheduler.xml

此外,可以通过spark-env.sh中的SPARK_WORKER_INSTANCES参数在一个物理节点上启动多个Worker进程,实现更粗粒度的资源隔离。

5.3 存储与Shuffle优化

Spark作业的性能瓶颈经常出现在Shuffle(数据混洗)和存储阶段。

  • Shuffle服务:启用外部Shuffle服务可以提升稳定性,尤其在动态资源分配时。在spark-env.sh中配置SPARK_WORKER_OPTS来启用,并确保防火墙开放相关端口。
  • 存储目录:我们之前配置了SPARK_WORKER_DIR。可以将其指向一个具有较大空间和较高IOPS的磁盘阵列,如SSD,以提升临时数据读写速度。甚至可以配置多个目录,用逗号分隔,Spark会进行负载均衡。
  • 序列化与压缩:在spark-defaults.conf中,使用Kryo序列化(spark.serializer org.apache.spark.serializer.KryoSerializer)通常比Java序列化更快更紧凑。对Shuffle输出进行压缩(spark.shuffle.compress true)可以减少网络传输量。

5.4 监控与日志管理

随着使用深入,需要更系统的监控。

  • Metrics系统:Spark可以通过JMX、Ganglia、Graphite等系统暴露大量度量指标。在spark-env.sh中配置SPARK_DAEMON_JAVA_OPTSSPARK_JAVA_OPTS来开启和指定接收服务器。
  • 日志聚合:默认日志分散在各个节点,排查问题不便。可以配置Spark使用Log4j或Logback将日志统一推送到像Elasticsearch + Kibana(ELK)或Graylog这样的集中式日志管理系统。这需要自定义log4j.properties文件。
  • 历史服务器:即使应用运行结束,我们仍可能想查看其运行详情。启动Spark History Server可以做到这一点。
    # 首先,需要配置事件日志目录。在spark-defaults.conf中设置: spark.eventLog.enabled true spark.eventLog.dir hdfs://master:9000/spark-logs # 或本地路径 file:///opt/spark/logs/events spark.history.fs.logDirectory hdfs://master:9000/spark-logs # 然后启动历史服务器 ./sbin/start-history-server.sh
    之后可以通过http://master:18080访问历史服务器界面。

6. 常见问题与故障排查实录

搭建和运行过程中,你几乎一定会遇到一些问题。下面是我总结的一些典型场景和排查思路。

6.1 集群启动失败问题排查表

问题现象可能原因排查步骤与解决方案
执行start-all.sh后,Master进程未启动。1. 端口冲突(如8080已被占用)。
2.spark-env.shJAVA_HOME配置错误。
3. 环境变量SPARK_HOME未正确设置。
1. 检查端口:netstat -tlnp | grep :8080,修改SPARK_MASTER_WEBUI_PORT
2. 检查java -versionecho $JAVA_HOME,确保路径存在且正确。
3. 检查$SPARK_HOME/sbin目录是否存在,脚本是否有执行权限。
Worker进程未在指定节点启动,Master UI中看不到Worker。1. SSH免密登录未配置成功。
2.workers文件中的主机名无法解析。
3. 目标节点防火墙未关闭,或端口被阻。
4. Worker节点上的spark-env.sh配置错误(如内存设置超出物理内存)。
1. 在Master节点手动ssh slave1测试,确认无需密码。
2. 在Master节点ping slave1测试网络和解析。
3. 检查Worker节点防火墙状态,确认SPARK_WORKER_PORT(默认8081)可访问。
4. 查看Worker节点的启动日志/opt/spark/logs/*.out,通常会有明确的错误信息。
Worker显示为DEAD或频繁断开重连。1. 网络不稳定,心跳超时。
2. Worker节点资源不足(如内存溢出),进程被系统杀死。
3. Master和Worker节点时间不同步。
1. 检查网络延迟和丢包率。
2. 查看Worker节点系统日志(dmesg/var/log/messages),检查是否有OOM Killer记录。适当调低SPARK_WORKER_MEMORY
3. 使用date命令检查各节点时间,确保NTP服务正常运行。
提交应用失败,连接被拒绝。1. 提交命令中--master地址错误。
2. Master进程未在7077端口监听。
3. 客户端网络无法访问Master节点。
1. 确认Master UI上显示的URL,确保提交命令与之一致。
2. 在Master节点执行netstat -tlnp | grep :7077,确认进程在监听。
3. 从客户端机器telnet master 7077测试连通性。

6.2 作业运行中的典型问题

问题:任务运行缓慢,或卡在某个阶段。

  • 排查
    1. 查看Web UI:进入该应用的Stages页面,查看是哪个Stage慢,是Task的GC时间长,还是Shuffle读写量大。
    2. 检查数据倾斜:在Stage详情页,查看每个Task的处理时间。如果某个Task时间远高于其他,很可能发生了数据倾斜。需要优化代码,如使用加盐的聚合操作。
    3. 检查资源利用率:在Worker UI或系统监控工具(如top)中,查看CPU、内存、磁盘IO和网络IO是否达到瓶颈。
    4. 查看日志:查看Executor的Stderr日志,看是否有大量的垃圾回收(GC)日志,频繁的Full GC会严重拖慢速度。

问题:出现OutOfMemoryError(内存溢出)。

  • 排查
    1. 区分类型:是Driver内存溢出还是Executor内存溢出?错误信息会指明。
    2. Driver OOM:通常是因为收集(collect)了过多数据到Driver端。应避免将大量数据拉取到Driver,或通过spark.driver.memory增加Driver内存。
    3. Executor OOM:可能是单个Task处理的数据分区过大,或者Shuffle过程中数据溢出。可以尝试:
      • 增加分区数:repartition
      • 增加Executor内存:spark.executor.memory
      • 调整内存比例:如增加spark.memory.fractionspark.shuffle.memoryFraction(取决于Spark版本)。
      • 检查代码中是否存在导致内存泄漏的集合引用。

6.3 独家避坑技巧

  1. 配置文件的“陷阱”spark-env.sh中,等号两边不能有空格,例如SPARK_MASTER_HOST=master是正确的,SPARK_MASTER_HOST = master会导致变量无法识别。Shell脚本对此很敏感。
  2. 主机名大小写:在所有配置文件和hosts文件中,尽量使用小写主机名,并保持完全一致。有些系统对大小写敏感,不一致会导致解析或连接失败。
  3. 防火墙的“回马枪”:如果你在云服务器(如AWS EC2, 阿里云ECS)上部署,除了系统防火墙,还要检查云服务商的安全组(Security Group)规则,确保开放了Master的7077、8080端口和Worker的8081、随机Executor端口范围(通常需要开放一个较大的端口段,如30000-50000)的入站访问。
  4. 资源分配的“跷跷板”:在YARN或K8s上部署Spark时,资源分配更为复杂。务必理解--executor-cores,--executor-memory,--num-executors这些参数与YARN队列资源、容器开销之间的关系。一个常见的错误是申请的内存超过了YARN NodeManager的可用物理内存,导致应用被直接拒绝。
  5. 日志是你的“第一现场”:遇到任何问题,不要慌,第一时间去查日志。Spark的日志输出比较详细,从进程启动失败到Task执行异常,基本都能在对应的.out.log文件中找到线索。养成看日志的习惯,是解决分布式系统问题的核心能力。

搭建一个稳定、高效的Spark完全分布式集群,是深入大数据领域的入场券。这个过程不仅仅是执行几条命令,更重要的是理解每个组件的作用、交互的原理和配置的影响。从最基础的Standalone模式开始,逐步扩展到考虑高可用、资源调度、性能监控,这是一个自然的学习和成长路径。希望这份详尽的指南,能帮你打下坚实的基础,少走一些我当年走过的弯路。记住,遇到问题多查日志,多思考组件间的联系,分布式系统的魅力就在于,当你理顺了这些连接,就能驾驭远超单机能力的计算力量。

← 返回列表