spark4 集群安装

spark4 集群安装

准备工作

创建安装目录

bash 复制代码
mkdir -p ~/opt
cd ~/opt

下载scala

bash 复制代码
wget -P ~/opt https://github.com/scala/scala/releases/download/v2.13.18/scala-2.13.18.tgz

解压scala

bash 复制代码
tar -zxvf ~/opt/scala-2.13.18.tgz -C ~/opt

修改scala目录名称

bash 复制代码
mv ~/opt/scala-2.13.18 ~/opt/scala-2

下载 JDK 21

Spark 4.1.2 要求 JDK 17 或 21,不支持 JDK 25。

bash 复制代码
wget -P ~/opt https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz

解压 JDK 21

bash 复制代码
tar -zxvf ~/opt/jdk-21_linux-x64_bin.tar.gz -C ~/opt

解压后目录名通常为 jdk-21.0.x,重命名为统一名称:

bash 复制代码
mv ~/opt/jdk-21.* ~/opt/jdk-21

下载spark

bash 复制代码
wget -P ~/opt https://dlcdn.apache.org/spark/spark-4.1.2/spark-4.1.2-bin-hadoop3-connect.tgz

解压spark

bash 复制代码
tar -zxvf ~/opt/spark-4.1.2-bin-hadoop3-connect.tgz  -C ~/opt

修改目录名称

bash 复制代码
mv  ~/opt/spark-4.1.2-bin-hadoop3-connect  ~/opt/spark-4

修改环境遍历

bash 复制代码
vim ~/.profile
bash 复制代码
export SCALA_HOME=$HOME/opt/scala-2

export SPARK_HOME=$HOME/opt/spark-4
export SPARKPYTHON=$HOME/opt/spark-4/python

export PATH=$PATH:$SCALA_HOME/bin:$SPARK_HOME/bin:$SPARK_HOME/sbin:$SPARKPYTHON

完整

bash 复制代码
export NODE_HOME=$HOME/opt/node-v24

export JAVA_HOME=$HOME/opt/jdk-25

export MAVEN_HOME=$HOME/opt/maven

export SCALA_HOME=$HOME/opt/scala-2

export ZOOKEEPER_HOME=$HOME/opt/zookeeper-3

export HDFS_NAMENODE_USER=lhz
export HDFS_SECONDARYNAMENODE_USER=lhz
export HDFS_DATANODE_USER=lhz
export HDFS_ZKFC_USER=lhz
export HDFS_JOURNALNODE_USER=lhz
export HADOOP_SHELL_EXECNAME=lhz

export YARN_RESOURCEMANAGER_USER=lhz
export YARN_NODEMANAGER_USER=lhz

export HADOOP_HOME=$HOME/opt/hadoop-3
export HADOOP_INSTALL=$HADOOP_HOME
export HADOOP_MAPRED_HOME=$HADOOP_HOME
export HADOOP_COMMON_HOME=$HADOOP_HOME
export HADOOP_HDFS_HOME=$HADOOP_HOME
export YARN_HOME=$HADOOP_HOME
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export JAVA_LIBRARY_PATH=$HADOOP_HOME/lib/native

export HIVE_HOME=/home/lhz/opt/hive-4
export HCATALOG_HOME=/home/lhz/opt/hive-4/hcatalog

export HBASE_HOME=$HOME/opt/hbase-2

export PHOENIX_HOME=$HOME/opt/phoenix

export KAFKA_HOME=$HOME/opt/kafka

export SPARK_HOME=$HOME/opt/spark-4
export SPARKPYTHON=$HOME/opt/spark-4/python

export PATH=$PATH:$NODE_HOME/bin:$JAVA_HOME/bin:$MAVEN_HOME/bin:$SCALA_HOME/bin:$ZOOKEEPER_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$HIVE_HOME/bin:$HCATALOG_HOME/bin:$HCATALOG_HOME/sbin:$HBASE_HOME/bin:$KAFKA_HOME/bin:$SPARK_HOME/bin:$SPARK_HOME/sbin:$SPARKPYTHON
bash 复制代码
source ~/.profile

Hadoop 集群(前置依赖)

本文档的 Standalone / HA 模式都用到了 Hadoop 的 HDFS 存储 Event Log,并提供了 YARN 模式提交作业。本节统一说明 Hadoop 集群架构与运维方式,便于初学者理解 Spark 与 Hadoop 的集成关系。

Hadoop 集群架构

当前环境使用 3 节点 Hadoop 完全分布式 + HA 集群:

复制代码
┌─────────────────────────────────────────────────────────────┐
│  Hadoop 集群 (3 节点完全分布式 + HA)                          │
│                                                              │
│  ┌──────────┐     ┌──────────┐     ┌──────────┐             │
│  │lihaozhe01│     │lihaozhe02│     │lihaozhe03│             │
│  │          │     │          │     │          │             │
│  │ NameNode │◄───►│ NameNode │     │          │             │
│  │  (nn1)   │ ZK  │  (nn2)   │     │          │             │
│  │  active  │ Fail│ standby  │     │          │             │
│  │          │ over     │          │          │             │
│  │ ZKFailoverController        │     │          │             │
│  │ DFSZKFailoverController     │     │          │             │
│  │ ResourceManager (rm1) active│     │          │             │
│  │          │     │ResourceManager (rm2) standby           │
│  │ NodeManager      │NodeManager     │NodeManager           │
│  │ DataNode         │DataNode        │DataNode              │
│  │ JournalNode      │JournalNode     │JournalNode           │
│  └──────────┘     └──────────┘     └──────────┘             │
│                                                              │
│  ZooKeeper 集群 (3 节点) - 用于 HDFS HA / YARN HA 选举       │
│  ZK 节点: lihaozhe01:2181, lihaozhe02:2181, lihaozhe03:2181 │
└─────────────────────────────────────────────────────────────┘

核心组件清单

组件 角色 节点 备注
NameNode HDFS 元数据管理 01 (active) / 02 (standby) 通过 ZK 选举
DFSZKFailoverController NameNode 健康监测与故障切换 01 / 02 每个 NameNode 配一个
JournalNode EditLog 共享存储(HA 同步) 01 / 02 / 03 3 节点多数派
DataNode HDFS 数据块存储 01 / 02 / 03 实际数据节点
ResourceManager YARN 资源调度 01 (active) / 02 (standby) 通过 ZK 选举
NodeManager YARN 单节点代理 01 / 02 / 03 容器管理
ZooKeeper 分布式协调服务(选举) 01 / 02 / 03 quorum 服务

Hadoop 启停命令

为避免与 Spark 启停脚本冲突,建议将 Hadoop 启停脚本重命名为 start-hadoop.sh / stop-hadoop.sh

bash 复制代码
cd /home/lhz/opt/hadoop-3/sbin
mv start-all.sh  start-hadoop.sh
mv stop-all.sh   stop-hadoop.sh

本文使用 start-hadoop.sh / stop-hadoop.sh,可与 Spark 的 start-spark.sh / stop-spark.sh 区分。

单独启停 HDFS
bash 复制代码
# 启动 HDFS(NameNode + DataNode + JournalNode + ZKFC)
start-hadoop.sh

# 仅启动 DataNode(在已部署的集群扩容新节点)
hdfs --daemon start datanode
单独启停 YARN
bash 复制代码
# 启动 YARN(ResourceManager + NodeManager)
start-yarn.sh

# 仅启动 NodeManager
yarn --daemon start nodemanager
完整启停(推荐)
bash 复制代码
# 启动顺序:ZK → HDFS → YARN
zkServer.sh start                      # 三节点分别执行
start-hadoop.sh                        # HDFS HA 集群
start-yarn.sh                          # YARN HA 集群

# 停止顺序:YARN → HDFS → ZK
stop-yarn.sh
stop-hadoop.sh
zkServer.sh stop                        # 三节点分别执行

Hadoop 服务状态查看

HDFS HA 状态
bash 复制代码
# 查看 NN1/NN2 状态
hdfs haadmin -getServiceState nn1
hdfs haadmin -getServiceState nn2

# 输出示例:
# active
# standby

# 查看集群健康
hdfs dfsadmin -report

# 查看 HDFS 容量
hdfs dfs -df -h
YARN HA 状态
bash 复制代码
# 查看 RM1/RM2 状态
yarn rmadmin -getServiceState rm1
yarn rmadmin -getServiceState rm2

# 查看集群节点
yarn node -list

# 查看集群资源
yarn top
ZooKeeper 状态
bash 复制代码
# 三节点分别查看
zkServer.sh status

HDFS 关键路径

路径 用途
hdfs://lihaozhe:8020/ HDFS 根目录(HA 命名服务)
hdfs://lihaozhe:8020/spark-log Spark Event Log 存储目录
hdfs://lihaozhe:8020/tmp 临时文件目录
hdfs://lihaozhe:8020/user 用户数据目录
hdfs://lihaozhe:8020/wordcount 示例数据(已存在)

Spark 与 Hadoop 集成的 4 个关键点

1. Event Log 写到 HDFS

$SPARK_HOME/conf/spark-defaults.conf 中:

properties 复制代码
spark.eventLog.enabled           true
spark.eventLog.dir               hdfs://lihaozhe/spark-log
spark.history.fs.logDirectory    hdfs://lihaozhe/spark-log

Spark 通过内置 hadoop-client 写日志到 HDFS(无需额外配置)。

2. Spark 客户端读 HDFS 配置

Spark 4.x 自带 hadoop-client,能从 core-site.xml / hdfs-site.xml 自动解析 HDFS HA。如果访问失败,在 spark-defaults.conf 中显式指定:

properties 复制代码
spark.hadoop.fs.defaultFS=hdfs://lihaozhe
3. YARN 模式提交

将作业提交到 YARN 集群(而非 Spark Standalone / HA 集群):

bash 复制代码
spark-submit --master yarn --deploy-mode client \
  --class org.apache.spark.examples.SparkPi \
  $SPARK_HOME/examples/jars/spark-examples_2.13-4.1.2.jar \
  100

此时 Driver 在客户端机器,Executor 在 YARN Container 中。

4. 共享 Hadoop 配置文件(可选)

如果 Spark 任务需要访问 Hive Metastore 或访问远端 HDFS,建议将 Hadoop 配置软链到 Spark 目录:

bash 复制代码
ln -s $HADOOP_HOME/etc/hadoop $SPARK_HOME/conf/hadoop-conf

这样 Spark 能自动加载 Hadoop 的所有配置。

Hadoop 配置示例(参考)

$HADOOP_HOME/etc/hadoop/core-site.xml
xml 复制代码
<configuration>
    <!-- HDFS 命名服务(HA 入口)-->
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://lihaozhe:8020</value>
    </property>

    <!-- ZK 集群(用于 NameNode HA)-->
    <property>
        <name>ha.zookeeper.quorum</name>
        <value>lihaozhe01:2181,lihaozhe02:2181,lihaozhe03:2181</value>
    </property>

    <!-- 临时目录 -->
    <property>
        <name>hadoop.tmp.dir</name>
        <value>/home/lhz/opt/hadoop-3/data/tmp</value>
    </property>
</configuration>
$HADOOP_HOME/etc/hadoop/hdfs-site.xml(HA 关键配置)
xml 复制代码
<configuration>
    <!-- NameNode 数据目录 -->
    <property>
        <name>dfs.namenode.name.dir</name>
        <value>file:///home/lhz/opt/hadoop-3/data/dfs/name</value>
    </property>

    <!-- DataNode 数据目录 -->
    <property>
        <name>dfs.datanode.data.dir</name>
        <value>file:///home/lhz/opt/hadoop-3/data/dfs/data</value>
    </property>

    <!-- HA 命名服务 -->
    <property>
        <name>dfs.nameservices</name>
        <value>lihaozhe</value>
    </property>

    <!-- 命名服务下的 NameNode ID -->
    <property>
        <name>dfs.ha.namenodes.lihaozhe</name>
        <value>nn1,nn2</value>
    </property>

    <!-- nn1 RPC 地址 -->
    <property>
        <name>dfs.namenode.rpc-address.lihaozhe.nn1</name>
        <value>lihaozhe01:8020</value>
    </property>

    <!-- nn2 RPC 地址 -->
    <property>
        <name>dfs.namenode.rpc-address.lihaozhe.nn2</name>
        <value>lihaozhe02:8020</value>
    </property>

    <!-- NameNode HTTP 页面 -->
    <property>
        <name>dfs.namenode.http-address.lihaozhe.nn1</name>
        <value>lihaozhe01:9870</value>
    </property>
    <property>
        <name>dfs.namenode.http-address.lihaozhe.nn2</name>
        <value>lihaozhe02:9870</value>
    </property>

    <!-- JournalNode 集群(共享 EditLog)-->
    <property>
        <name>dfs.namenode.shared.edits.dir</name>
        <value>qjournal://lihaozhe01:8485;lihaozhe02:8485;lihaozhe03:8485/lihaozhe</value>
    </property>

    <!-- HA 自动切换代理 -->
    <property>
        <name>dfs.client.failover.proxy.provider.lihaozhe</name>
        <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
    </property>

    <!-- 启用自动故障切换 -->
    <property>
        <name>dfs.ha.automatic-failover.enabled</name>
        <value>true</value>
    </property>

    <!-- JournalNode 数据目录 -->
    <property>
        <name>dfs.journalnode.edits.dir</name>
        <value>/home/lhz/opt/hadoop-3/data/journalnode</value>
    </property>
</configuration>
$HADOOP_HOME/etc/hadoop/yarn-site.xml(HA 关键配置)
xml 复制代码
<configuration>
    <!-- YARN HA 启用 -->
    <property>
        <name>yarn.resourcemanager.ha.enabled</name>
        <value>true</value>
    </property>

    <!-- YARN 命名服务 -->
    <property>
        <name>yarn.resourcemanager.cluster-id</name>
        <value>yarn-ha</value>
    </property>

    <!-- RM 列表 -->
    <property>
        <name>yarn.resourcemanager.ha.rm-ids</name>
        <value>rm1,rm2</value>
    </property>

    <!-- rm1 主机 -->
    <property>
        <name>yarn.resourcemanager.hostname.rm1</name>
        <value>lihaozhe01</value>
    </property>

    <!-- rm2 主机 -->
    <property>
        <name>yarn.resourcemanager.hostname.rm2</name>
        <value>lihaozhe02</value>
    </property>

    <!-- ZK 集群 -->
    <property>
        <name>yarn.resourcemanager.zk-address</name>
        <value>lihaozhe01:2181,lihaozhe02:2181,lihaozhe03:2181</value>
    </property>

    <!-- NodeManager 本地目录 -->
    <property>
        <name>yarn.nodemanager.local-dirs</name>
        <value>/home/lhz/opt/hadoop-3/data/nm-local</value>
    </property>

    <!-- 日志目录 -->
    <property>
        <name>yarn.nodemanager.log-dirs</name>
        <value>/home/lhz/opt/hadoop-3/logs/yarn/userlogs</value>
    </property>
</configuration>

常见问题

HDFS 进入 Safe Mode
bash 复制代码
# 查看 Safe Mode 状态
hdfs dfsadmin -safemode get

# 强制离开 Safe Mode(数据不一致时慎用)
hdfs dfsadmin -safemode leave
HDFS 磁盘空间不足
bash 复制代码
# 查看各 DataNode 磁盘使用
hdfs dfsadmin -report

# 删除无用文件
hdfs dfs -rm -r /path/to/unwanted/data
YARN Container 启动失败
bash 复制代码
# 查看 NM 日志
yarn logs -applicationId <application_id>

# 或直接在 NM 节点查看
tail -f $HADOOP_HOME/logs/yarn/yarn-lhz-nodemanager-*.log

配置 spark-env.sh

Spark 4.1.2 要求 JDK 17 或 21,需在 conf/spark-env.sh 中指定 JDK 路径并设置 Netty 内存分配器参数。

bash 复制代码
cd $SPARK_HOME/conf
cp spark-env.sh.template spark-env.sh
vim spark-env.sh

在文件末尾添加以下内容:

bash 复制代码
# ---- JDK ----
export JAVA_HOME=$HOME/opt/jdk-21

# ---- JVM 参数(关键)----
export SPARK_DAEMON_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS -Dio.netty.allocator.type=unpooled"
export SPARK_SUBMIT_OPTS="$SPARK_SUBMIT_OPTS -Dio.netty.allocator.type=unpooled"

SPARK_DAEMON_JAVA_OPTS 对 Spark 守护进程(包括 Connect Server)生效,SPARK_SUBMIT_OPTS 对通过 spark-submit 提交的客户端生效。

本地开发模式(Spark Connect)

注:Spark 4.1.2 已将传统 local[*] 单机模式整合进 Spark Connect 架构,本节内容对应本地启动 Connect Server + 远程客户端的开发方式。如需传统单机模式(无 Server,进程内运行 Spark),可通过 spark-submit --master 'local[*]' 直接提交,或使用 YARN / Standalone 集群模式。

Spark 4.1.2 提供了 Spark Connect 安装包(spark-4.1.2-bin-hadoop3-connect.tgz),该包采用 Server-Client 架构

  • Server 端:启动一个常驻的 Spark Connect Server,负责执行计算任务
  • Client 端 :通过远程协议(sc://)连接 Server,提交代码,无需本地 SparkContext

这种模式适合本地开发测试,客户端只需引入轻量的 Connect 依赖,无需完整的 Spark 安装。

启动 Spark Connect Server

bash 复制代码
cd $SPARK_HOME
./sbin/start-connect-server.sh --master 'local[*]' --conf spark.connect.grpc.binding.port=15002
  • --master 'local[*]':使用本地模式,使用所有可用的 CPU 核心
  • spark.connect.grpc.binding.port=15002:指定 gRPC 端口(默认 15002)
  • 启动后日志位于 $SPARK_HOME/logs/ 目录下

验证 Server 是否启动成功:

bash 复制代码
tail -f $SPARK_HOME/logs/spark-*.out

日志末尾应看到类似 INFO SparkConnectServer: SparkConnectServer started. 的信息。

停止 Spark Connect Server

bash 复制代码
cd $SPARK_HOME
./sbin/spark-daemon.sh stop org.apache.spark.sql.connect.service.SparkConnectServer 1

Python 项目开发与测试

创建项目目录
bash 复制代码
mkdir -p ~/opt/code/demo-python/src
cd ~/opt/code/demo-python
创建虚拟环境
bash 复制代码
python3 -m venv venv
source venv/bin/activate
安装依赖
bash 复制代码
pip install pyspark pyarrow pandas zstandard

注:不指定版本号,自动安装最新兼容版本。

编写客户端代码

创建 src/app.py

python 复制代码
from pyspark.sql import SparkSession

def main():
    remote = "sc://localhost:15002"
    print(f"[INFO] 连接 Spark Connect Server: {remote}")

    spark = SparkSession.builder.remote(remote).getOrCreate()

    # 验证 1: 执行 SQL
    print("\n[TEST] spark.range(5).show()")
    spark.range(0, 5).show()

    # 验证 2: DataFrame 操作
    print("\n[TEST] spark.sql('SELECT 1+1 AS result')")
    spark.sql("SELECT 1+1 AS result").show(truncate=False)

    # 验证 3: 创建 DataFrame
    print("\n[TEST] spark.createDataFrame([(1,'a'),(2,'b')], ['id','val'])")
    df = spark.createDataFrame([(1, "a"), (2, "b")], ["id", "val"])
    df.show()

    # 验证 4: 版本信息
    print(f"\n[INFO] Spark version: {spark.version}")

    spark.stop()
    print("\n[OK] 全部测试通过!")

if __name__ == "__main__":
    main()

使用 SparkSession.builder.remote("sc://localhost:15002") 通过 Spark Connect 协议连接 Server,无需本地 SparkContext。

运行 Python 客户端
bash 复制代码
cd ~/opt/code/demo-python
source venv/bin/activate
python src/app.py

预期输出:

复制代码
[INFO] 连接 Spark Connect Server: sc://localhost:15002

[TEST] spark.range(5).show()
+---+
| id|
+---+
|  0|
|  1|
|  2|
|  3|
|  4|
+---+

[TEST] spark.sql('SELECT 1+1 AS result')
+------+
|result|
+------+
|2     |
+------+

[TEST] spark.createDataFrame([(1,'a'),(2,'b')], ['id','val'])
+---+---+
| id|val|
+---+---+
|  1|  a|
+---+---+

[INFO] Spark version: 4.1.2

[OK] 全部测试通过!

Scala / Maven 项目开发与测试

创建项目目录
bash 复制代码
mkdir -p ~/opt/code/demo-maven/src/main/scala/demo
cd ~/opt/code/demo-maven
编写 pom.xml
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>demo.lhz</groupId>
    <artifactId>demo-maven</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <name>demo-maven</name>
    <description>Spark 4.1.2 Connect Client demo (Scala 2.13.18)</description>

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <scala.version>2.13.18</scala.version>
        <scala.binary.version>2.13</scala.binary.version>
        <spark.version>4.1.2</spark.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-connect-client-jvm_${scala.binary.version}</artifactId>
            <version>${spark.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>demo-maven-1.0</finalName>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>4.9.10</version>
                <executions>
                    <execution>
                        <id>scala-compile-first</id>
                        <phase>process-resources</phase>
                        <goals><goal>compile</goal></goals>
                    </execution>
                    <execution>
                        <id>scala-test-compile</id>
                        <phase>process-test-resources</phase>
                        <goals><goal>testCompile</goal></goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>${scala.version}</scalaVersion>
                    <scalaCompatVersion>${scala.binary.version}</scalaCompatVersion>
                    <args>
                        <arg>-feature</arg>
                        <arg>-deprecation</arg>
                    </args>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.15.0</version>
                <configuration>
                    <release>${maven.compiler.release}</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.5.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>demo.Hello</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

关键依赖:spark-connect-client-jvm_2.13 是 Spark Connect 的 Scala 客户端库,scope 设为 provided,运行时由 Server 端提供。

编写 Scala 客户端代码

创建 src/main/scala/demo/Hello.scala

scala 复制代码
package demo

import org.apache.spark.sql.SparkSession

object Hello {
  def main(args: Array[String]): Unit = {
    val remote = "sc://localhost:15002"
    val spark = SparkSession.builder().remote(remote).getOrCreate()

    spark.range(0, 5).show()
    spark.sql("SELECT 1+1 AS result").show(truncate = false)

    import spark.implicits._
    val df = Seq((1, "a"), (2, "b")).toDF("id", "val")
    df.show()

    println(s"[INFO] Spark version: ${spark.version}")
    spark.stop()
  }
}
编译打包
bash 复制代码
cd ~/opt/code/demo-maven
mvn clean package

编译成功后会在 target/ 目录下生成 demo-maven-1.0.jar

提交运行
bash 复制代码
spark-submit --remote "sc://localhost:15002" --class demo.Hello target/demo-maven-1.0.jar

预期输出:

复制代码
+---+
| id|
+---+
|  0|
|  1|
|  2|
|  3|
|  4|
+---+

+------+
|result|
+------+
|2     |
+------+

+---+---+
| id|val|
+---+---+
|  1|  a|
|  2|  b|
+---+---+

[INFO] Spark version: 4.1.2

小结

Spark Connect 本地模式的工作流程:

复制代码
┌─────────────────┐     sc://localhost:15002     ┌──────────────────┐
│  Client (Python) │ ──────────────────────────▶  │  Spark Connect   │
│  or (Scala/Java) │                              │  Server          │
│                  │ ◀──────────────────────────  │  (local[*])      │
│  spark-submit    │       结果返回               │  Spark 4.1.2     │
└─────────────────┘                              └──────────────────┘
  • Server:启动一次,常驻运行,负责执行所有计算任务
  • Client :通过 sc:// 协议远程提交代码,无需本地 Spark 环境
  • 适合本地开发测试,代码与集群模式完全兼容,迁移到生产环境只需修改连接地址

Standalone模式

Spark 4.x Standalone 模式配置文件名为 workers(原 SPARK 3.x 的 slaves 已弃用)。本文以三节点为例:lihaozhe01(Master + Worker)、lihaozhe02(Worker)、lihaozhe03(Worker)。前提:三节点已部署 JDK 21、Scala 2.13.18、Hadoop 3.x,且 HDFS 已启动(用于 Event Log),可通过 ssh lhz@lihaozhe02 / ssh lhz@lihaozhe03 免密钥互访。

重命名 sbin 脚本(区分 Hadoop 启停)

为了避免与 Hadoop 的 start-all.sh / stop-all.sh 冲突,将 Spark 启停脚本重命名:

bash 复制代码
cd $SPARK_HOME/sbin
mv start-all.sh start-spark.sh
mv stop-all.sh stop-spark.sh

进入配置目录

bash 复制代码
cd $SPARK_HOME/conf

编写 spark-env.sh

bash 复制代码
cp spark-env.sh.template spark-env.sh
vim spark-env.sh

在文件末尾追加:

bash 复制代码
# ---- JDK 21(Spark 4.x 不支持 JDK 25)----
export JAVA_HOME=/home/lhz/opt/jdk-21

# ---- Netty 内存分配器(JDK 17+ 关键参数)----
export SPARK_DAEMON_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS -Dio.netty.allocator.type=unpooled"
export SPARK_SUBMIT_OPTS="$SPARK_SUBMIT_OPTS -Dio.netty.allocator.type=unpooled"

# ---- Standalone Master / Worker ----
export SPARK_MASTER_HOST=lihaozhe01
export SPARK_MASTER_PORT=7077
export SPARK_MASTER_WEBUI_PORT=8080

export SPARK_WORKER_MEMORY=2g
export SPARK_WORKER_CORES=2
export SPARK_WORKER_WEBUI_PORT=8081

# ---- History Server ----
export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080
-Dspark.history.retainedApplications=30
-Dspark.history.fs.logDirectory=hdfs://lihaozhe/spark-log"

SPARK_WORKER_MEMORY / SPARK_WORKER_CORES 限制的是该 Worker 节点可分配给 Executors 的总内存与核数,按本机资源调整。

编辑 workers

bash 复制代码
cp workers.template workers
vim workers
bash 复制代码
lihaozhe01
lihaozhe02
lihaozhe03

配置 spark-defaults.conf

bash 复制代码
cp spark-defaults.conf.template spark-defaults.conf
vim spark-defaults.conf
bash 复制代码
# ---- 启用 Event Log ----
spark.eventLog.enabled           true
spark.eventLog.dir               hdfs://lihaozhe/spark-log
spark.eventLog.compress.codec    snappy

# ---- History Server 日志目录 ----
spark.history.fs.logDirectory    hdfs://lihaozhe/spark-log

创建 HDFS 日志目录

bash 复制代码
hdfs dfs -mkdir -p /spark-log
hdfs dfs -chmod 1777 /spark-log

分发到其他节点

使用 rsync 同步(排除 logs/work/pids/):

bash 复制代码
rsync -a --delete \
  --exclude='logs/*' --exclude='work/*' --exclude='pids/*' \
  /home/lhz/opt/spark-4/ lhz@lihaozhe02:/home/lhz/opt/spark-4/

rsync -a --delete \
  --exclude='logs/*' --exclude='work/*' --exclude='pids/*' \
  /home/lhz/opt/spark-4/ lhz@lihaozhe03:/home/lhz/opt/spark-4/

验证配置已同步:

bash 复制代码
ssh lhz@lihaozhe02 "ls /home/lhz/opt/spark-4/sbin/start-spark.sh /home/lhz/opt/spark-4/conf/workers"
ssh lhz@lihaozhe03 "ls /home/lhz/opt/spark-4/sbin/start-spark.sh /home/lhz/opt/spark-4/conf/workers"

启动集群

确保 HDFS 已启动(通过 start-hadoop.sh),然后:

bash 复制代码
$SPARK_HOME/sbin/start-spark.sh
$SPARK_HOME/sbin/start-history-server.sh

启动日志输出示例:

复制代码
starting org.apache.spark.deploy.master.Master, logging to /home/lhz/opt/spark-4/logs/spark-lhz-org.apache.spark.deploy.master.Master-1-lihaozhe01.out
lihaozhe03: starting org.apache.spark.deploy.worker.Worker, ...
lihaozhe02: starting org.apache.spark.deploy.worker.Worker, ...
lihaozhe01: starting org.apache.spark.deploy.worker.Worker, ...
starting org.apache.spark.deploy.history.HistoryServer, logging to ...

验证集群状态

通过 curl 检查所有 UI:

bash 复制代码
curl -s -o /dev/null -w "Master UI: %{http_code}\n" http://lihaozhe01:8080/
curl -s -o /dev/null -w "Worker 01 UI: %{http_code}\n" http://lihaozhe01:8081/
curl -s -o /dev/null -w "Worker 02 UI: %{http_code}\n" http://lihaozhe02:8081/
curl -s -o /dev/null -w "Worker 03 UI: %{http_code}\n" http://lihaozhe03:8081/
curl -s -o /dev/null -w "History UI: %{http_code}\n" http://lihaozhe01:18080/

全部应返回 200

通过 Master REST API 查看集群状态:

bash 复制代码
curl -s http://lihaozhe01:8080/json/ | python3 -c "
import json, sys
d = json.load(sys.stdin)
for w in d.get('workers', []):
    print(f\"  {w['host']} state={w['state']} cores={w['cores']} mem={w['memory']}MB\")
"

Web UI

提交作业(三种方式验证集群)

方式一:spark-submit 内置示例(SparkPi)

关键:Spark 4.1.2 的 connect 包中 spark-submit 默认走 Connect 模式(SPARK_CONNECT_MODE=1),需设置 SPARK_CONNECT_MODE=0 强制走集群模式。

bash 复制代码
cd $SPARK_HOME
export SPARK_CONNECT_MODE=0
spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master spark://lihaozhe01:7077 \
  --deploy-mode client \
  ./examples/jars/spark-examples_2.13-4.1.2.jar \
  100

预期输出末尾:

复制代码
Pi is roughly 3.1415339141533916
方式二:Scala 项目(demo-maven)
bash 复制代码
cd ~/opt/code/demo-maven
mvn clean package -DskipTests

export SPARK_CONNECT_MODE=0
spark-submit \
  --class demo.Hello \
  --master spark://lihaozhe01:7077 \
  --deploy-mode client \
  target/demo-maven-1.0.jar

预期输出:

复制代码
+---+
| id|
+---+
|  0|
|  1|
|  2|
|  3|
|  4|
+---+

+------+
|result|
+------+
|2     |
+------+

+---+---+
| id|val|
+---+---+
|  1|  a|
|  2|  b|
+---+---+

[INFO] Spark version: 4.1.2
[OK] 全部测试通过!
方式三:Spark Connect Server + Python 客户端

启动 Connect Server(连接到 Standalone 集群):

bash 复制代码
$SPARK_HOME/sbin/start-connect-server.sh \
  --master spark://lihaozhe01:7077 \
  --conf spark.connect.grpc.binding.port=15002

激活 venv 并运行 Python 客户端:

bash 复制代码
cd ~/opt/code/demo-python
source venv/bin/activate
python src/app.py

预期输出:

复制代码
[INFO] 连接 Spark Connect Server: sc://localhost:15002

[TEST] spark.range(5).show()
+---+
| id|
+---+
|  0|
|  1|
|  2|
|  3|
|  4|
+---+

[INFO] Spark version: 4.1.2
[OK] 全部测试通过!

提交作业到 Standalone 集群(cluster 模式)

bash 复制代码
cd $SPARK_HOME
export SPARK_CONNECT_MODE=0
spark-submit \
  --master spark://lihaozhe01:7077 \
  --deploy-mode cluster \
  --class org.apache.spark.examples.SparkPi \
  ./examples/jars/spark-examples_2.13-4.1.2.jar \
  10

停止集群

bash 复制代码
$SPARK_HOME/sbin/stop-spark.sh
$SPARK_HOME/sbin/stop-history-server.sh

# 停止 Connect Server
$SPARK_HOME/sbin/spark-daemon.sh stop org.apache.spark.sql.connect.service.SparkConnectServer 1

集群状态实测

完成上述三种方式测试后,Master UI 显示:

复制代码
Workers:
  - 192.168.10.101 state=ALIVE cores=2 mem=2048MB
  - 192.168.10.102 state=ALIVE cores=2 mem=2048MB
  - 192.168.10.103 state=ALIVE cores=2 mem=2048MB

Active Apps:
  - Spark Connect server cores=6  # 占用了 3 worker × 2 cores

关于 SPARK_CONNECT_MODE

Spark 4.1.2 spark-4.1.2-bin-hadoop3-connect.tgz 包中的 spark-submit 脚本开头为:

bash 复制代码
export SPARK_CONNECT_MODE=${SPARK_CONNECT_MODE:-1}

这意味着:

  • 默认值 1:使用 Connect 模式(启动一个临时 Connect Server,客户端走 gRPC),此时不能与 --master 同时使用
  • 设置为 0:使用传统模式(driver 端直连集群),可与 --master 同时使用

如需省略每次都输入 export SPARK_CONNECT_MODE=0,可在 ~/.bashrc 中添加:

bash 复制代码
export SPARK_CONNECT_MODE=0

或者针对 Spark Connect 包彻底改回传统行为(不推荐,丧失 Connect 能力)。

HA模式(基于 ZooKeeper)

Spark 4.x HA 模式通过 ZooKeeper 实现 Master 主备切换。所有 Master 节点共享同一份 ZooKeeper 集群配置,任意时刻只有一个 Active Master,其余为 Standby。本文以三节点为例:lihaozhe01 / lihaozhe02 / lihaozhe03 均部署 Master 和 Worker。前提:ZooKeeper 集群(3 节点)已启动并通过 zkServer.sh status 验证一主两备。

前置环境 :HDFS 集群(start-hadoop.sh 启动) + ZooKeeper 集群(zkServer.sh start 三节点启动)。

完整配置文件

HA 模式复用 Standalone 模式的 workersspark-defaults.conf,仅需修改 spark-env.sh 加入 ZK HA 配置。下列三个文件可直接复制使用。

配置文件 1:$SPARK_HOME/conf/spark-env.sh(完整内容)
bash 复制代码
#!/usr/bin/env bash

#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# This file is sourced when running various Spark programs.
# Copy it as spark-env.sh and edit that to configure Spark for your site.

# Options read when launching programs locally with
# ./bin/run-example or ./bin/spark-submit
# - HADOOP_CONF_DIR, to point Spark towards Hadoop configuration files
# - SPARK_LOCAL_IP, to set the IP address Spark binds to on this node
# - SPARK_PUBLIC_DNS, to set the public dns name of the driver program

# Options read by executors and drivers running inside the cluster
# - SPARK_LOCAL_IP, to set the IP address Spark binds to on this node
# - SPARK_PUBLIC_DNS, to set the public DNS name of the driver program
# - SPARK_LOCAL_DIRS, storage directories to use on this node for shuffle and RDD data

# Options read in any mode
# - SPARK_CONF_DIR, Alternate conf dir. (Default: ${SPARK_HOME}/conf)
# - SPARK_EXECUTOR_CORES, Number of cores for the executors (Default: 1).
# - SPARK_EXECUTOR_MEMORY, Memory per Executor (e.g. 1000M, 2G) (Default: 1G)
# - SPARK_DRIVER_MEMORY, Memory for Driver (e.g. 1000M, 2G) (Default: 1G)

# Options read in any cluster manager using HDFS
# - HADOOP_CONF_DIR, to point Spark towards Hadoop configuration files

# Options read in YARN client/cluster mode
# - YARN_CONF_DIR, to point Spark towards YARN configuration files when you use YARN

# Options for the daemons used in the standalone deploy mode
# - SPARK_MASTER_HOST, to bind the master to a different IP address or hostname
# - SPARK_MASTER_PORT / SPARK_MASTER_WEBUI_PORT, to use non-default ports for the master
# - SPARK_MASTER_OPTS, to set config properties only for the master (e.g. "-Dx=y")
# - SPARK_WORKER_CORES, to set the number of cores to use on this machine
# - SPARK_WORKER_MEMORY, to set how much total memory workers have to give executors (e.g. 1000m, 2g)
# - SPARK_WORKER_PORT / SPARK_WORKER_WEBUI_PORT, to use non-default ports for the worker
# - SPARK_WORKER_DIR, to set the working directory of worker processes
# - SPARK_WORKER_OPTS, to set config properties only for the worker (e.g. "-Dx=y")
# - SPARK_DAEMON_MEMORY, to allocate to the master, worker and history server themselves (default: 1g).
# - SPARK_HISTORY_OPTS, to set config properties only for the history server (e.g. "-Dx=y")
# - SPARK_SHUFFLE_OPTS, to set config properties only for the external shuffle service (e.g. "-Dx=y")
# - SPARK_DAEMON_JAVA_OPTS, to set config properties for all daemons (e.g. "-Dx=y")
# - SPARK_DAEMON_CLASSPATH, to set the classpath for all daemons
# - SPARK_PUBLIC_DNS, to set the public dns name of the master or workers

# Options for launcher
# - SPARK_LAUNCHER_OPTS, to set config properties and Java options for the launcher (e.g. "-Dx=y")

# Generic options for the daemons used in the standalone deploy mode
# - SPARK_CONF_DIR      Alternate conf dir. (Default: ${SPARK_HOME}/conf)
# - SPARK_LOG_DIR       Where log files are stored.  (Default: ${SPARK_HOME}/logs)
# - SPARK_LOG_MAX_FILES Max log files of Spark daemons can rotate to. Default is 5.
# - SPARK_PID_DIR       Where the pid file is stored. (Default: /tmp)
# - SPARK_IDENT_STRING  A string representing this instance of spark. (Default: $USER)
# - SPARK_NICENESS      The scheduling priority for daemons. (Default: 0)
# - SPARK_NO_DAEMONIZE  Run the proposed command in the foreground. It will not output a PID file.
# Options for native BLAS, like Intel MKL, OpenBLAS, and so on.
# You might get better performance to enable these options if using native BLAS (see SPARK-21305).
# - MKL_NUM_THREADS=1        Disable multi-threading of Intel MKL
# - OPENBLAS_NUM_THREADS=1   Disable multi-threading of OpenBLAS

# Options for beeline
# - SPARK_BEELINE_OPTS, to set config properties only for the beeline cli (e.g. "-Dx=y")
# - SPARK_BEELINE_MEMORY, Memory for beeline (e.g. 1000M, 2G) (Default: 1G)

# ======================================================================
# ---- Custom: HA 模式完整配置(基于 Spark 4.1.2-bin-hadoop3-connect)----
# ======================================================================

# ---- JDK 21(Spark 4.x 不支持 JDK 25)----
export JAVA_HOME=/home/lhz/opt/jdk-21

# ---- Netty 内存分配器(JDK 17+ 关键参数)----
export SPARK_DAEMON_JAVA_OPTS="-Dio.netty.allocator.type=unpooled"
export SPARK_SUBMIT_OPTS="-Dio.netty.allocator.type=unpooled"

# ---- Standalone Master / Worker ----
# HA 模式下 SPARK_MASTER_HOST 不生效,Master 通过 ZK 选举产生
export SPARK_MASTER_PORT=7077
export SPARK_MASTER_WEBUI_PORT=8080

export SPARK_WORKER_MEMORY=2g
export SPARK_WORKER_CORES=2
export SPARK_WORKER_WEBUI_PORT=8081

# ---- History Server ----
export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080
-Dspark.history.retainedApplications=30
-Dspark.history.fs.logDirectory=hdfs://lihaozhe/spark-log"

# ---- ZooKeeper HA ----
# SPARK_DAEMON_JAVA_OPTS 包含所有 Spark 守护进程(Master/Worker/HistoryServer)的 JVM 参数
export SPARK_DAEMON_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS \
  -Dspark.deploy.recoveryMode=ZOOKEEPER \
  -Dspark.deploy.zookeeper.url=lihaozhe01:2181,lihaozhe02:2181,lihaozhe03:2181 \
  -Dspark.deploy.zookeeper.dir=/spark-ha"
配置文件 2:$SPARK_HOME/conf/workers(完整内容)
复制代码
lihaozhe01
lihaozhe02
lihaozhe03
配置文件 3:$SPARK_HOME/conf/spark-defaults.conf(完整内容)
properties 复制代码
# ---- 启用 Event Log ----
spark.eventLog.enabled           true
spark.eventLog.dir               hdfs://lihaozhe/spark-log
spark.eventLog.compress.codec    snappy

# ---- History Server 日志目录 ----
spark.history.fs.logDirectory    hdfs://lihaozhe/spark-log
一次性分发到 02/03
bash 复制代码
rsync -a /home/lhz/opt/spark-4/conf/spark-env.sh        lhz@lihaozhe02:/home/lhz/opt/spark-4/conf/spark-env.sh
rsync -a /home/lhz/opt/spark-4/conf/spark-env.sh        lhz@lihaozhe03:/home/lhz/opt/spark-4/conf/spark-env.sh
rsync -a /home/lhz/opt/spark-4/conf/workers             lhz@lihaozhe02:/home/lhz/opt/spark-4/conf/workers
rsync -a /home/lhz/opt/spark-4/conf/workers             lhz@lihaozhe03:/home/lhz/opt/spark-4/conf/workers
rsync -a /home/lhz/opt/spark-4/conf/spark-defaults.conf lhz@lihaozhe02:/home/lhz/opt/spark-4/conf/spark-defaults.conf
rsync -a /home/lhz/opt/spark-4/conf/spark-defaults.conf lhz@lihaozhe03:/home/lhz/opt/spark-4/conf/spark-defaults.conf

启动 HA 集群

1. 启动 ZooKeeper 集群(如果未启动)

三节点分别执行:

bash 复制代码
zkServer.sh start
zkServer.sh status

验证选举结果:

bash 复制代码
for h in lihaozhe01 lihaozhe02 lihaozhe03; do
  echo "$h: $(ssh lhz@$h 'echo srvr | nc localhost 2181' | grep Mode)"
done

应看到类似:

复制代码
lihaozhe01: Mode: follower
lihaozhe02: Mode: follower
lihaozhe03: Mode: leader
2. 在三个节点分别启动 Master

关键:HA 模式下不能用 start-spark.sh(原 start-all.sh),它只在本节点启动 Master,其他 Master 需要手动启动。

bash 复制代码
# 在 lihaozhe01
$SPARK_HOME/sbin/start-master.sh

# 在 lihaozhe02
ssh lhz@lihaozhe02 "$HOME/opt/spark-4/sbin/start-master.sh"

# 在 lihaozhe03
ssh lhz@lihaozhe03 "$HOME/opt/spark-4/sbin/start-master.sh"
3. 验证 ZK 选举结果
bash 复制代码
for h in lihaozhe01 lihaozhe02 lihaozhe03; do
  s=$(curl -s "http://$h:8080/json/" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status','?'))")
  echo "  $h: status=$s"
done

预期输出(任一台为 ALIVE,其余为 STANDBY):

复制代码
lihaozhe01: status=ALIVE
lihaozhe02: status=STANDBY
lihaozhe03: status=STANDBY
4. 在三个节点分别启动 Worker
bash 复制代码
for h in lihaozhe01 lihaozhe02 lihaozhe03; do
  ssh lhz@$h "$HOME/opt/spark-4/sbin/start-worker.sh spark://lihaozhe01:7077,lihaozhe02:7077,lihaozhe03:7077"
done

验证集群状态:

bash 复制代码
curl -s "http://lihaozhe01:8080/json/" | python3 -c "
import json, sys
d = json.load(sys.stdin)
print(f'Master: {d[\"status\"]}')
for w in d.get('workers', []):
    print(f'  Worker: {w[\"host\"]} cores={w[\"cores\"]} mem={w[\"memory\"]}MB')
"
5. 启动 History Server
bash 复制代码
$SPARK_HOME/sbin/start-history-server.sh

Web UI

提交作业(三种方式验证)

关键:HA 模式下 spark-submit 必须使用多 Master URL,客户端会自动尝试所有 Master,找到 Active Master 后提交。

方式一:spark-submit 内置示例(SparkPi)
bash 复制代码
cd $SPARK_HOME
export SPARK_CONNECT_MODE=0
spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master spark://lihaozhe01:7077,lihaozhe02:7077,lihaozhe03:7077 \
  --deploy-mode client \
  ./examples/jars/spark-examples_2.13-4.1.2.jar \
  100

预期输出末尾:

复制代码
Pi is roughly 3.14...
方式二:Scala 项目(demo-maven)
bash 复制代码
cd ~/opt/code/demo-maven
mvn clean package -DskipTests

export SPARK_CONNECT_MODE=0
spark-submit \
  --class demo.Hello \
  --master spark://lihaozhe01:7077,lihaozhe02:7077,lihaozhe03:7077 \
  --deploy-mode client \
  target/demo-maven-1.0.jar

预期输出末尾:[OK] 全部测试通过!

方式三:Spark Connect Server + Python 客户端

启动 Connect Server(连接到 HA 集群):

bash 复制代码
$SPARK_HOME/sbin/start-connect-server.sh \
  --master spark://lihaozhe01:7077,lihaozhe02:7077,lihaozhe03:7077 \
  --conf spark.connect.grpc.binding.port=15002

激活 venv 并运行 Python 客户端:

bash 复制代码
cd ~/opt/code/demo-python
source venv/bin/activate
python src/app.py

预期输出末尾:[OK] 全部测试通过!

验证 HA 切换(实测通过)

测试 1:杀 Active Master,观察自动切换

初始状态

复制代码
lihaozhe01: status=ALIVE  workers=3
lihaozhe02: status=STANDBY
lihaozhe03: status=STANDBY

Kill 掉 lihaozhe01 上的 Active Master

bash 复制代码
MASTER_PID=$(ssh lhz@lihaozhe01 "ps -ef | grep -E 'java.*deploy.master.Master' | grep -v grep | awk '{print \$2}'")
ssh lhz@lihaozhe01 "kill $MASTER_PID"

等待约 30 秒后查看选举状态

bash 复制代码
sleep 30
for h in lihaozhe01 lihaozhe02 lihaozhe03; do
  resp=$(curl -s "http://$h:8080/json/" 2>/dev/null)
  s=$(echo "$resp" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status','no-resp'))" 2>/dev/null)
  workers=$(echo "$resp" | python3 -c "import json,sys; print(len(json.load(sys.stdin).get('workers',[])))" 2>/dev/null)
  echo "  $h: status=$s workers=$workers"
done

切换后状态

复制代码
lihaozhe01: status=            (进程已死)
lihaozhe02: status=ALIVE  workers=3   # 自动接管
lihaozhe03: status=STANDBY
测试 2:故障切换后提交新作业

lihaozhe01 Master 死掉后,把它的 Master 重启回到 STANDBY,然后用 Python + Connect 验证集群仍可用:

bash 复制代码
# 重启 01 Master(回到集群,重新选举为 STANDBY)
ssh lhz@lihaozhe01 "$HOME/opt/spark-4/sbin/start-master.sh"

# 重启 Connect Server(绑定到当前 ALIVE Master,避免旧 session 卡住)
$HOME/opt/spark-4/sbin/spark-daemon.sh stop org.apache.spark.sql.connect.service.SparkConnectServer 1
$SPARK_HOME/sbin/start-connect-server.sh \
  --master spark://lihaozhe02:7077 \
  --conf spark.connect.grpc.binding.port=15002

# 运行 Python 客户端
cd ~/opt/code/demo-python && source venv/bin/activate && python src/app.py

预期输出末尾:[OK] 全部测试通过! --- 证明故障切换后集群仍能正常处理新作业。

注意事项
  • 运行中的作业:Active Master 故障切换时,正在运行的作业会失败(driver 端与 Master 的 session 丢失),但集群可继续接收新作业。如需运行中作业不中断,应使用 cluster 模式 + driver 资源预留。
  • Connect Server 重连:Connect Server 内部会缓存与 Active Master 的 session,切换后需要重启 Connect Server 绑定新 Master。
  • 旧 Master 重新加入:被 kill 的 Master 重启后默认加入为 STANDBY,不会自动抢占 Active。

停止 HA 集群

bash 复制代码
# 1. 停止 Connect Server(如有)
$SPARK_HOME/sbin/spark-daemon.sh stop org.apache.spark.sql.connect.service.SparkConnectServer 1

# 2. 在所有节点停止 Worker
for h in lihaozhe01 lihaozhe02 lihaozhe03; do
  ssh lhz@$h "$HOME/opt/spark-4/sbin/stop-worker.sh"
done

# 3. 在所有节点停止 Master
for h in lihaozhe01 lihaozhe02 lihaozhe03; do
  ssh lhz@$h "$HOME/opt/spark-4/sbin/stop-master.sh"
done

# 4. 停止 History Server
$SPARK_HOME/sbin/stop-history-server.sh

HA 集群状态实测

完成上述所有测试后,HA 集群状态:

复制代码
Master 状态:
  - lihaozhe01: STANDBY   (曾为 Active,被 kill 后重启回 STANDBY)
  - lihaozhe02: ALIVE     (自动接管的 Active Master)
  - lihaozhe03: STANDBY

Workers:
  - 192.168.10.101 (lihaozhe01) cores=2 mem=2048MB
  - 192.168.10.102 (lihaozhe02) cores=2 mem=2048MB
  - 192.168.10.103 (lihaozhe03) cores=2 mem=2048MB

集群总资源: 6 cores / 6144MB
相关推荐
wuhanzhanhui4 小时前
重塑供应链!2026武汉数字孪生产业展览会亮相,打造工业未来新基石
大数据·人工智能
数智化管理手记5 小时前
智能财务如何减少财务加班?智能财务落地需要哪些工具支撑?
大数据·网络·数据库·数据挖掘·精益工程
A15362556 小时前
国内好用的WMS仓储管理系统有哪些?万里牛WMS深度评测
大数据·数据库·人工智能
码云数智-园园6 小时前
2026主流SaaS小程序搭建平台对比
大数据
Elastic 中国社区官方博客8 小时前
4 个英伟达人工智能任务,1 个 Elasticsearch 接口:嵌入、聊天、completion 和重排序
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai
融智兴科技8 小时前
一张校园卡学生证,如何连接学习、生活与校园管理?
大数据·科技·物联网
Geeys9 小时前
淘宝新店一般要熬几个月?淘宝新店破周期提速实操方案
大数据·人工智能
麦兜和小可的舅舅9 小时前
从原理到实战:Linux 系统性能诊断核心指标全解析及生产系统故障分析复盘
大数据·linux·运维
阿里技术10 小时前
Agent 评测:方法论与体系设计
大数据·人工智能·算法