Apache Spark Connect 概览:解耦式客户端-服务端架构的完整实战指南
【免费下载链接】sparkApache Spark - A unified analytics engine for large-scale data processing项目地址: https://gitcode.com/gh_mirrors/sp/spark
导读
本文以 Apache Spark 官方文档《Spark Connect Overview》为骨架,系统讲解 Spark 3.4 引入的 Spark Connect 解耦式客户端-服务端架构:它如何用「未解析逻辑计划 + Protocol Buffers + gRPC + Arrow」把轻量客户端与 Spark 引擎彻底分离,以及如何在 PySpark 与 Scala 中快速搭建 Connect 服务器、配置客户端连接、在独立应用中使用 Spark Connect,并处理多租户稳定性、认证、流式 API 支持与反向代理路由等落地问题。读完本文,你将掌握从零启动 Spark Connect Server、用sc://连接串在交互式 Shell 与独立程序中开发,以及让 Spark Connect 安全接入 Kubernetes Ingress 的完整技术方案。
一、什么是 Spark Connect
Spark Connect 是 Apache Spark 3.4 中引入的客户端-服务端解耦架构,它允许通过DataFrame API 和未解析逻辑计划(unresolved logical plans)作为协议,实现到 Spark 集群的远程连接。客户端与服务端的分离,使得 Spark 及其开放生态可以从任何地方被调用——它可以被嵌入现代数据应用、IDE、Notebook 以及各类编程语言之中。
其核心思想是:客户端库不再包含完整的 Spark 引擎,而是一个可以随处嵌入的薄 API(thin API)。Spark Connect API 构建在 Spark 的 DataFrame API 之上,以未解析逻辑计划作为客户端与 Spark Driver 之间**与语言无关(language-agnostic)**的协议。
官方快速入门入口参见 Quickstart: Spark Connect(该页面位于仓库
docs/api/python/文档体系内)。
Spark Connect API 架构图
上图展示了 Spark Connect 的 API 架构:左侧是多样化的客户端入口(现代数据应用、IDE/Notebook、多语言 SDK),右侧是服务端 Spark Driver 内部的多租户应用网关(Multi-tenant Application Gateway)、分析器(Analyzer)、优化器(Optimizer)、调度器(Scheduler)与分布式执行引擎(Distributed Execution Engine)。客户端只负责调用 API,全部计算逻辑在服务端完成。
二、Spark Connect 的工作原理
Spark Connect 客户端库的设计目标是简化 Spark 应用程序开发。其工作流程如下:
- 计划构建:Spark Connect 客户端把 DataFrame 操作翻译为未解析的逻辑查询计划(unresolved logical query plan)。
- 协议编码:这些计划使用Protocol Buffers(protobuf)进行编码。
- 传输:编码后的计划通过gRPC 框架发送到服务器。
- 服务端解析:Spark Connect 端点在 Spark Server 上接收并将未解析的逻辑计划翻译为 Spark 的逻辑计划算子。这与解析一条 SQL 查询类似——属性(attributes)和关系(relations)被解析,构建出初始解析计划(initial parse plan)。
- 标准执行:随后标准 Spark 执行流程启动,从而保证 Spark Connect 能继承 Spark 全部优化与增强能力(Analyzer → Optimizer → Scheduler → Distributed Execution Engine)。
- 结果回传:执行结果通过 gRPC 以Apache Arrow 编码的行批次(row batches)流式返回给客户端。
Spark Connect 通信流程图
从仓库源码看,服务端入口由sbin/start-connect-server.sh启动,其核心类为org.apache.spark.sql.connect.service.SparkConnectServer(参见 start-connect-server.sh),而请求的实际处理逻辑位于 SparkConnectService.scala,它实现了 protobuf 定义的SparkConnectServicegRPC 服务(如ExecutePlan等 RPC 方法)。
2.1 Spark Connect 客户端应用与传统 Spark 应用的区别
Spark Connect 的核心设计目标之一是实现客户端与服务端的完全分离与隔离。因此开发者需要注意以下几点变化:
- 客户端不与 Spark Driver 运行在同一进程中:客户端无法直接访问 Driver JVM 来操纵执行环境。特别是在 PySpark 中,客户端不再使用 Py4J,因此无法访问持有 DataFrame、Column、SparkSession 等 JVM 实现私有字段(例如
df._jdf)。 - 协议基于逻辑计划、不支持全部执行 API:Spark Connect 协议以 Spark 逻辑计划作为抽象,用声明式方式描述要执行的运算,因此它不支持 Spark 的全部执行 API,最重要的是不支持 RDD。
- 基于会话(session-based)的客户端:客户端无法访问会操纵所有已连接客户端共享集群环境的属性。最重要的是,客户端无法访问静态 Spark 配置或 SparkContext。
三、Spark Connect 的运维收益
新架构为多租户环境缓解了以下经典运维问题:
- 稳定性(Stability):内存使用过高的应用只会影响自身环境,因为它们可以在各自独立的进程中运行。用户可以在客户端自行定义依赖,无需担心与 Spark Driver 产生依赖冲突。
- 可升级性(Upgradability):Spark Driver 可以独立于应用无缝升级,例如受益于性能改进与安全补丁。只要服务端 RPC 定义设计为向后兼容,应用就能保持前向兼容(forward-compatible)。
- 可调试性与可观测性(Debuggability and observability):Spark Connect 支持在开发阶段直接从你喜欢的 IDE 中进行交互式调试;同时,应用可以使用其所在框架原生的指标(metrics)与日志库进行监控。
四、如何启动 Spark Server 并启用 Spark Connect
Spark Connect 目前支持PySpark 与 Scala两类应用。下面演示如何运行一个带 Spark Connect 的 Spark Server,并让客户端应用通过 Spark Connect 客户端库连接它。
4.1 下载并启动带 Spark Connect 的 Spark Server
首先从 Apache Spark 官方下载页面获取 Spark 发行包,选择最新版本以及适合的包类型(通常选择 "Pre-built for Apache Hadoop 3.5 and later")。下载后解压:
tar -xvf spark-3.5.x-bin-hadoop3.tgz打开终端,进入解压后的spark目录,运行start-connect-server.sh脚本启动带 Spark Connect 的 Spark Server:
./sbin/start-connect-server.sh说明:请确保所用包版本与下载的 Spark 版本一致。仓库中 start-connect-server.sh 的脚本逻辑是:通过
spark-daemon.sh submit org.apache.spark.sql.connect.service.SparkConnectServer以后台守护进程方式提交 Connect 服务器,且支持--wait参数以前台方式运行(便于调试)。
除独立服务器外,还可以使用./bin/spark-connect-shell启动一个交互式 Scala Shell,其Connect 服务器内嵌在 Shell 进程内部,适合本地快速体验。
启动完成后,Spark Server 即处于运行状态,可以接受来自客户端应用的 Spark Connect 会话。
4.2 用于交互式分析的 Spark Connect
创建 Spark 会话时可以通过多种方式指定使用 Spark Connect。若未使用以下任一机制,Spark 会话仍将和以前一样工作(即不启用 Spark Connect)。
方式一:设置SPARK_REMOTE环境变量
在客户端机器上设置SPARK_REMOTE环境变量后创建新 Spark 会话,该会话即为 Spark Connect 会话。这种方式无需改动任何代码。
export SPARK_REMOTE="sc://localhost" ./bin/pysparkPySpark Shell 启动后,欢迎信息会提示已通过 Spark Connect 连接:
Client connected to the Spark Connect server at localhost方式二:创建 Spark 会话时显式指定
也可以在启动 PySpark Shell 时通过remote参数指定服务器位置:
./bin/pyspark --remote "sc://localhost"同样可以看到欢迎信息提示已连接。还可以检查会话类型来确认——如果类型路径包含.connect.即表示在使用 Spark Connect:
SparkSession available as 'spark'. >>> type(spark) <class 'pyspark.sql.connect.session.SparkSession'>然后即可正常运行 PySpark 代码验证 Spark Connect 生效:
>>> columns = ["id", "name"] >>> data = [(1,"Sarah"), (2,"Maria")] >>> df = spark.createDataFrame(data).toDF(*columns) >>> df.show() +---+-----+ | id| name| +---+-----+ | 1|Sarah| | 2|Maria| +---+-----+Scala Shell 方式
Scala Shell 基于 Ammonite REPL,用法与 PySpark Shell 类似:
./bin/spark-shell --remote "sc://localhost"REPL 初始化成功后会显示欢迎横幅,并默认尝试连接本机 Spark Server:
Welcome to ____ __ / __/__ ___ _____/ /__ _\ \/ _ \/ _ `/ __/ '_/ /___/ .__/\_,_/_/ /_/\_\ version 3.5.x /_/ Type in expressions to have them evaluated. Spark session available as 'spark'.运行 Scala 代码验证:
@ spark.range(10).count res0: Long = 10L4.3 配置客户端-服务器连接
默认情况下,REPL 会尝试连接本机 15002 端口的 Spark Server(该端口也是sc://连接串的默认端口)。连接可以通过以下几种方式配置(完整参数规范见仓库 client-connection-string.md)。
设置SPARK_REMOTE环境变量,在 REPL 启动时自定义客户端-服务器连接:
export SPARK_REMOTE="sc://myhost.com:443/;token=ABCDEFG" ./bin/spark-shell或直接内联:
SPARK_REMOTE="sc://myhost.com:443/;token=ABCDEFG" spark-connect-repl通过连接字符串以编程方式创建连接,使用SparkSession#builder:
@ import org.apache.spark.sql.SparkSession @ val spark = SparkSession.builder.remote("sc://localhost:443/;token=ABCDEFG").getOrCreate()连接字符串参数详解
sc://连接串遵循标准 URI 定义(scheme 固定为sc://),路径组件必须为空,参数通过 HTTP URL Path Parameter 语法传递,且所有参数区分大小写:
sc://host:port/;param1=value;param2=value| 参数 | 类型 | 说明 | 示例 |
|---|---|---|---|
| host | String | Spark Connect 端点主机名,必须为完整域名或 IP 地址(gRPC 端点不支持路径) | myexample.com、127.0.0.1 |
| port | Numeric | gRPC 端点端口,默认 15002 | 15002、443 |
| token | String | 设置后启用基于标准 bearer token 的 gRPC 认证;设置该值会同时启用 SSL | token=ABCDEFGH |
| use_ssl | Boolean | 使用 TLS 连接,默认false;证书需在系统信任库中 | use_ssl=true |
| user_id | String | 自动写入 Spark Connect UserContext 消息的用户 ID,用于 Spark 会话管理,可选 | user_id=Martin |
| user_agent | String | 代表用户执行请求的应用标识,Python 客户端默认_SPARK_CONNECT_PYTHON | user_agent=my_data_query_app |
| session_id | String | 服务端会话缓存的键,可用于跨语言共享同一 Spark 会话,须为合法 UUID,默认随机生成 | session_id=550e8400-e29b-41d4-a716-446655440000 |
| grpc_max_message_size | Numeric | gRPC 消息最大字节数,默认128 * 1024 * 1024 | grpc_max_message_size=134217728 |
| grpc_keepalive_enabled | Boolean | 是否发送 gRPC/HTTP2 keepalive PING 以探测静默死连接(如 NAT 网关/负载均衡丢弃空闲连接),默认true | grpc_keepalive_enabled=false |
| grpc_keepalive_time_ms | Numeric | 空闲多少毫秒后发送 keepalive PING。服务端容忍客户端 PING 频率不低于每 10s 一次,低于该下限会被以too_many_pings断开 | grpc_keepalive_time_ms=30000 |
| grpc_keepalive_timeout_ms | Numeric | 等待 PING 确认的毫秒数,超时判定连接死亡 | grpc_keepalive_timeout_ms=10000 |
| grpc_keepalive_without_calls | Boolean | 无在途 RPC 时是否仍持续发送 keepalive PING,默认true | grpc_keepalive_without_calls=false |
典型用法示例:
server_url = "sc://myhost.com/" # 默认端口 15002 server_url = "sc://myhost.com:443/;use_ssl=true" # 换端口 + TLS server_url = "sc://myhost.com:443/;use_ssl=true;token=ABCDEFG" # TLS + 令牌认证 server_url = "sc://myhost.com:443/;grpc_keepalive_time_ms=30000;grpc_keepalive_timeout_ms=10000" # 加速探测死连接需要特别注意的是:连接串中不能携带路径前缀(如sc://myhost.com:443/mypathprefix/;token=AAAAAAA是非法用法),因为 gRPC 的方法名本身就是 HTTP/2 的:path,客户端无法指定独立路由路径,这在下文「反向代理路由」一节还会详述。
五、更快地本地迭代:持久化 Connect 服务器
在本地开发或测试时,如果用:
from pyspark.sql import SparkSession spark = SparkSession.builder.remote("local[*]").getOrCreate()PySpark 会启动一个仅在当前 Python 进程存活期间存在的全新进程内 Connect 服务器。每次执行python script.py(或每个新的测试 worker 进程)都要重新支付一次性启动成本——JVM 预热、SparkContext构建、Connect 服务器启动——这些往往需要数秒,让「改代码 → 跑一下」的循环变得缓慢。
5.1 手动启动持久化服务器
为了摊销这一成本,可以启动一个持久化的本地 Connect 服务器,并让每次运行都连接它:
# 只启动一次,跨多次运行保持在线。(--master 可选,默认 local[*]) $SPARK_HOME/sbin/start-connect-server.sh --master "local[*]" # 每次运行都重连,而不是重新启动一个新服务器 python -c 'from pyspark.sql import SparkSession; SparkSession.builder.remote("sc://localhost:15002").getOrCreate()' # 用完停止 $SPARK_HOME/sbin/stop-connect-server.sh5.2 由 PySpark 托管的持久化服务器(实验性)
在 POSIX 系统上,PySpark 可以代为管理这个持久化服务器。设置SPARK_LOCAL_CONNECT_REUSE=1(或 builder 上的spark.local.connect.reuse=true)后,SparkSession.builder.remote("local[*]").getOrCreate()会在首次运行时通过sbin/start-connect-server.sh启动一个持久化服务器,后续运行则直接重连,脚本里可以一直使用普通的local[*]URL:
export SPARK_LOCAL_CONNECT_REUSE=1 # 第一次运行启动服务器;后续运行重连 python -c 'from pyspark.sql import SparkSession; SparkSession.builder.remote("local[*]").getOrCreate()' # 完成时停止托管服务器 python -m pyspark.sql.connect.local_server --stop从源码看,该机制的实现在 local_server.py:
- 托管服务器是一个普通的
spark-daemon.sh守护进程,但它运行在按用户隔离的 pid 目录与标识串下,因此不会与手工启动的服务器冲突——这也意味着普通的sbin/stop-connect-server.sh找不到它。--stop命令会向记录的服务器发送信号并清理发现文件;直接 kill 服务器 pid 同样有效,下一次运行发现服务器已死会自动启动新实例。 - 连接细节(host、port、认证 token、pid、Spark 版本)记录在每个用户私有的发现文件中,可用
SPARK_LOCAL_CONNECT_DISCOVERY覆盖其位置。发现文件与日志存放于系统临时目录下按用户隔离的0700权限目录,token 以0600权限存储,且服务器总是绑定 IPv4 loopback(覆盖任何配置的绑定地址),因此机器上其他用户既读不到 token 也无法认证——同用户的不同进程按设计共享服务器。 - 一次运行只重连Spark 版本匹配的服务器。升级 Spark 后,旧版本服务器无法复用,下一次运行会报错并提示停止旧服务器;执行上面的
--stop后重跑即可启动新服务器。 - 会话隔离语义:每次运行都建立独立的 Connect 会话,因此会话级状态——临时视图、运行时 SQL 配置、会话产物——每次运行都是全新的,绝不会在运行间泄漏;而由共享
SparkContext支撑的状态(持久化 catalog/warehouse、全局临时视图、缓存的数据集)会在运行间共享,因此若要求运行间完全隔离,请自行按运行命名空间划分数据库或清理这些状态。 - 该托管工作流目前是实验性的:
--stop命令、发现文件位置与格式可能在未来版本变化(例如本地服务器管理被并入统一的spark connectCLI)。该机制依赖sbin/下的 POSIX 脚本,Windows 不支持。
六、在独立应用程序中使用 Spark Connect
6.1 Python(PySpark)独立应用
首先安装客户端包。可以单独安装轻量客户端(不含完整引擎):
pip install pyspark-client==3.5.x如果是打包 PySpark 应用/库,在setup.py中声明依赖:
install_requires=[ 'pyspark-client==3.5.x' ]编写代码时,创建 Spark 会话时通过remote函数引用你的 Spark Server:
from pyspark.sql import SparkSession spark = SparkSession.builder.remote("sc://localhost").getOrCreate()下面是一个完整的简单应用SimpleApp.py——统计一个文本文件中包含字母a与b的行数:
"""SimpleApp.py""" from pyspark.sql import SparkSession logFile = "YOUR_SPARK_HOME/README.md" # Should be some file on your system spark = SparkSession.builder.remote("sc://localhost").appName("SimpleApp").getOrCreate() logData = spark.read.text(logFile).cache() numAs = logData.filter(logData.value.contains('a')).count() numBs = logData.filter(logData.value.contains('b')).count() print("Lines with a: %i, lines with b: %i" % (numAs, numBs)) spark.stop()注意:需要将
YOUR_SPARK_HOME替换为 Spark 实际安装路径。
用普通 Python 解释器运行:
$ python SimpleApp.py ... Lines with a: 72, lines with b: 396.2 Scala 独立应用
在 Scala 应用/工程中使用 Spark Connect,首先需要引入正确的依赖。以sbt为例,在build.sbt中添加:
libraryDependencies += "org.apache.spark" %% "spark-connect-client-jvm" % "3.5.x"编写代码时同样通过remote函数创建会话:
import org.apache.spark.sql.SparkSession val spark = SparkSession.builder().remote("sc://localhost").getOrCreate()关于用户自定义代码的重要说明:涉及引用用户自定义代码的操作(如 UDF、filter、map等)需要注册一个 ClassFinder 来拾取并上传所需的 class 文件;同时,任何 JAR 依赖都必须通过SparkSession#addArtifact上传到服务器。示例:
import org.apache.spark.sql.connect.client.REPLClassDirMonitor // 注册一个 ClassFinder 来监视并上传构建输出目录中的 class 文件 val classFinder = new REPLClassDirMonitor(<ABSOLUTE_PATH_TO_BUILD_OUTPUT_DIR>) spark.registerClassFinder(classFinder) // 上传 JAR 依赖 spark.addArtifact(<ABSOLUTE_PATH_JAR_DEP>)其中,ABSOLUTE_PATH_TO_BUILD_OUTPUT_DIR是构建系统写出 class 文件的输出目录,ABSOLUTE_PATH_JAR_DEP是本地文件系统上 JAR 的位置。REPLClassDirMonitor是ClassFinder的一个内置实现,用于监视指定目录;你也可以继承ClassFinder实现自定义的搜索与监视逻辑。
关于 Spark Connect 应用开发与自定义扩展的更多内容,参见仓库文档 Application Development with Spark Connect。
七、客户端应用认证
Spark Connect没有内置认证机制,但它被设计为可以无缝对接你现有的认证基础设施。其 gRPC HTTP/2 接口支持使用认证代理(authenticating proxies),因此可以在不向 Spark 中直接实现认证逻辑的情况下保护 Spark Connect。
八、支持范围(What is supported)
PySpark:自 Spark 3.4 起,Spark Connect 支持大部分 PySpark API,包括 DataFrame、Functions 与 Column。但不支持SparkContext、RDD 等 API。可以在 PySpark 的 API reference 文档中逐项核对:被标注为"Supports Spark Connect"的 API 即表示已支持,迁移既有代码前可据此检查。
Scala:自 Spark 3.5 起,Spark Connect 支持大部分 Scala API,包括 Dataset、functions、Column、Catalog 与 KeyValueGroupedDataset。
UDF:用户自定义函数(User-Defined Functions)受支持——在 Shell 中默认支持,在独立应用中则需要额外配置(即上文提到的 ClassFinder 与 AddArtifact)。
流式 API:大部分 Streaming API 受支持,包括 DataStreamReader、DataStreamWriter、StreamingQuery 与 StreamingQueryListener。
不支持:SparkContext 与 RDD 在 Spark Connect 中均不受支持。更多 API 支持正在规划中,预计在未来的 Spark 版本中推出。
九、通过共享入口或反向代理路由(Ingress/Reverse Proxy)
当多个服务在 Kubernetes Ingress(或其他反向代理)后面共享同一主机名时,一个常见的想法是给每个服务一个 URL 路径前缀(例如sc://host/sparkConnect)并基于路径路由。这种做法对 gRPC 行不通:gRPC 方法名本身就是 HTTP/2 的:path(例如/spark.connect.SparkConnectService/ExecutePlan),因此连接串无法携带独立的路由路径。给:path加前缀会产生未知方法,服务器会返回UNIMPLEMENTED——除非代理被配置为在转发前把前缀剥掉,而这是一个不属于 gRPC 设计范畴的双边契约。
真正自由的路由维度是 HTTP/2 的:authority(虚拟主机)。做法是:通过grpc.default_authoritychannel 选项把它设为一个路由标签(routing tag),然后在代理侧基于它路由(例如 Ingress 的host:规则)。客户端仍然拨号共享主机名(default_authority只覆盖用于路由的:authority头,不会改变实际连接的地址),而 gRPC 方法的:path从未被改动,因此无需任何路径重写。这正是 gRPC 维护者针对该场景推荐的做法。
下面的示例使用 Python 客户端,它可以直接暴露 gRPC channel 选项:
from pyspark.sql.connect.session import SparkSession from pyspark.sql.connect.client import DefaultChannelBuilder cb = DefaultChannelBuilder("sc://myhost.com:443") cb.setChannelOption("grpc.default_authority", "sparkconnect") # 路由标签 spark = SparkSession.builder.channelBuilder(cb).getOrCreate()对应的 Kubernetes Ingress 基于该标签路由,并让服务保持在/(无子路径、无重写)。注意路由标签必须是小写的 RFC 1123 名称,因为 Kubernetes Ingress 的host:字段有此要求:
spec: rules: - host: sparkconnect # 匹配 grpc.default_authority http: paths: - path: / pathType: Prefix backend: service: name: spark-connect-server port: { number: 15002 }在 TLS 场景下,gRPC 默认把:authority用作证书校验名称,因此用路由标签覆盖它会导致校验失败(标签不在服务器证书的 SAN 中)。解决办法是让两个名称各司其职:用grpc.ssl_target_name_override指定真实服务器主机名(用于证书校验与 SNI),用grpc.default_authority指定路由标签:
cb = DefaultChannelBuilder("sc://myhost.com:443/;use_ssl=true") cb.setChannelOption("grpc.ssl_target_name_override", "myhost.com") # 证书校验 / SNI cb.setChannelOption("grpc.default_authority", "sparkconnect") # 路由标签 spark = SparkSession.builder.channelBuilder(cb).getOrCreate()几点补充说明:
use_ssl=true会用系统可信 CA 库校验服务器证书。如果你的网关证书由客户端默认不信任的 CA 签发(自签名或内部 CA),应在客户端侧让该 CA 受信任(例如通过GRPC_DEFAULT_SSL_ROOTS_FILE_PATH环境变量),而不是通过连接串解决。代理自身的 TLS 配置(对哪个主机名出示哪个证书)属于 Ingress 配置范畴,不在本文讨论范围。- 关于
grpc.ssl_target_name_override的两个要点:gRPC 将其文档化为面向测试的选项,因为其典型误用是掩盖证书名称不匹配(用一个服务器并未实际出示的名称去校验,从而破坏主机名校验)。而在上述场景中它被设置为真实、已校验的主机名,证书仍被正确检查,只是避免路由标签被用作校验名。如果你希望完全避开该选项,可以给服务器证书的 SAN 中直接加上路由标签——此时仅设置grpc.default_authority即可,无需任何 override。
十、核心源码与文档索引
如需进一步深入,可在仓库中查阅以下关键路径:
- 服务端入口脚本:sbin/start-connect-server.sh
- 服务端 gRPC 实现:SparkConnectService.scala
- 连接串规范:sql/connect/docs/client-connection-string.md
- Scala 客户端 ClassFinder 接口:ClassFinder.scala
- Python 本地持久化服务器实现:python/pyspark/sql/connect/local_server.py
- Spark Connect 应用开发进阶指南:docs/app-dev-spark-connect.md
适用前提说明:本文中的交互示例(SPARK_REMOTE、--remote参数、pyspark/spark-shell)与独立应用示例均要求先按「四、」一节启动对应版本的 Spark Connect 服务器,且客户端与服务端使用相同的 Spark 版本;pyspark-client与spark-connect-client-jvm的版本号请替换为与你的服务器一致的实际版本。持久化服务器复用(SPARK_LOCAL_CONNECT_REUSE)目前为实验特性,仅支持 POSIX 系统。
【免费下载链接】sparkApache Spark - A unified analytics engine for large-scale data processing项目地址: https://gitcode.com/gh_mirrors/sp/spark
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考