每天认识一个组件:统一 Shuffle 引擎Apache Uniffle

一、引言

在 Spark、MapReduce、Tez 等分布式计算任务中,Shuffle 往往是最容易放大性能瓶颈和稳定性问题的环节。在传统 Spark Shuffle 中,Map 端会把中间数据落在 Executor 所在节点的本地磁盘上,Reduce 端再跨节点拉取这些数据。这个模式在中小规模任务中足够直接,但在大规模集群、动态资源调度、云原生环境和超大 Shuffle 作业中,会暴露几个典型问题。

  • Shuffle 数据和计算资源强绑定。Executor 退出、节点下线、磁盘损坏或本地目录被清理,都可能影响后续 Reduce 端读取。
  • 大量 Reduce 端并发拉取 Map 输出时,会带来连接数膨胀和随机 I/O 压力。
  • 在云原生和弹性资源场景中,计算 Pod 或 Executor 的生命周期更短,Shuffle 数据如果仍然绑在计算节点上,动态伸缩会受到限制。

Apache Uniffle 的核心思路是把 Shuffle 数据从计算节点本地磁盘中解耦出来,交给独立的远程 Shuffle 服务管理,从而减少连接数、降低随机 I/O、缓解大规模任务中的内存或磁盘空间失败,并提升资源编排弹性。

远程 Shuffle 的关键变化不是"把本地磁盘换成 HDFS"这么简单,而是引入一层专门的 Shuffle 服务,把写入、缓存、合并、索引、容错和存储选择从计算引擎中剥离出来。

二、Uniffle 是什么

Apache Uniffle 是一个 Remote Shuffle Service,它为 Apache Spark 应用提供把 Shuffle 数据存储到远程服务器的能力。 同时,Uniffle 也被描述为面向多个计算框架的统一 Shuffle 引擎,已经提供 Spark 和 MapReduce 的可插拔客户端插件来启用远程 Shuffle。

从用户视角看,Uniffle 不是替代 Spark、MapReduce 或 Tez 的计算引擎,而是替代或增强这些引擎的 Shuffle 层。应用仍然由 Spark Driver、Executor、Map Task、Reduce Task 执行;变化在于 Shuffle 数据不再主要依赖 Executor 本地目录,而是通过 Uniffle 客户端写入 Shuffle Server,并按配置落到内存、本地磁盘、HDFS 等存储介质。

Uniffle 的定位概括为:高性能、通用的远程 Shuffle 服务,面向分布式计算引擎。 这意味着它的目标不是解决所有计算性能问题,而是聚焦 Shuffle 这个高 I/O、高网络、高状态管理复杂度的环节。

三、架构组成

Uniffle 的核心架构包含三类组件:Coordinator 集群、Shuffle Server 集群,以及必要时使用的远程存储,例如 HDFS。Coordinator 是控制面,Shuffle Server 是数据面。Coordinator 负责收集 Shuffle Server 状态,并为作业分配 Shuffle Server;Shuffle Server 负责接收 Shuffle 数据、合并数据并写入存储。

四、Shuffle 读写流程

Uniffle 的 Shuffle 写入流程可以分成客户端缓冲、发送、服务端缓存、刷盘、索引记录和读取几个阶段。

Spark Driver 会先向 Coordinator 请求 Shuffle Server;Spark Task 写 Shuffle 数据时,会先把 KV 数据写入 Buffer,在 Buffer 满或 Buffer Manager 满时刷入队列,再由线程池处理发送。Shuffle Server 会先把数据缓存到内存,在 Buffer Manager 满时刷入队列,最终把数据以 index file 和 data file 的形式写入存储;写完后,Task 会向 Shuffle Server 上报所有 blockId,用于后续数据校验。

复制代码
Uniffle Shuffle 写入链路

Spark Driver
    |
    | 1. 向 Coordinator 请求 Shuffle Server 分配
    v
Coordinator
    |
    | 2. 返回可用 Shuffle Server
    v
Spark Map Task
    |
    | 3. KV 写入客户端 Buffer
    v
Client Buffer / Queue
    |
    | 4. 请求 Shuffle Server 内存并发送数据
    v
Shuffle Server Memory
    |
    | 5. 内存缓存,达到条件后进入 flush queue
    v
Flush Thread Pool
    |
    | 6. 写入 Local Disk / HDFS / Hybrid Storage
    v
Data File + Index File
    |
    | 7. 上报 blockId,用于后续校验
    v
Shuffle Metadata

读取时,Spark Task 会根据不同存储类型,从 Shuffle Server、远程存储或二者同时读取 Shuffle 数据。 这也是 Uniffle 与传统本地 Shuffle 的重要区别:读取路径由服务端和存储模式共同决定,而不是固定从 Executor 本地磁盘拉取。

Uniffle 的 Shuffle 数据以 data file 和 index file 存储:data file 保存特定 partition 的所有 blocks,index file 保存每个 block 的元数据。 这种文件组织方式有助于读端按 partition 和 block 元信息定位数据。

复制代码
Shuffle 文件格式示意

Partition N

data file
+---------+---------+---------+---------+
| Block 1 | Block 2 | Block 3 | Block 4 |
+---------+---------+---------+---------+

index file
+---------+--------+--------+----------+
| BlockId | Offset | Length | Checksum |
+---------+--------+--------+----------+
|   B1    |   0    |  128K  |   CRC    |
|   B2    | 128K   |  256K  |   CRC    |
|   B3    | 384K   |   64K  |   CRC    |
+---------+--------+--------+----------+

五、存储模式

Uniffle 支持多种存储模式:Memory & Local、Memory & Remote Storage、Memory & Local & Remote Storage,生产环境推荐 Memory & Local & Remote Storage。

在配置中,对应的rss.storage.type支持MEMORY_LOCALFILE、MEMORY_HDFS、MEMORY_LOCALFILE_HDFS。

|----------------------------|-----------------------|----------------------|------------------|
| 存储模式 | 典型配置 | 适合场景 | 主要取舍 |
| Memory + Local File | MEMORY_LOCALFILE | 本地磁盘充足、追求较低延迟 | 依赖本地磁盘容量和健康状态 |
| Memory + HDFS | MEMORY_HDFS | 本地磁盘受限、希望借助远程存储 | 网络与 HDFS 写入性能更关键 |
| Memory + Local File + HDFS | MEMORY_LOCALFILE_HDFS | 生产环境、大小 Shuffle 混合负载 | 配置和调优复杂度更高 |

Uniffle 的一个设计原则是同时处理小 Shuffle 和大 Shuffle,因此 Shuffle Server 会把小 block 写到本地磁盘,把大 block 写到 HDFS,阈值由 rss.server.flush.cold.storage.threshold.size 配置;建议根据 workload 和存储系统情况调节该参数,使远程写和本地写比例适配实际负载。

这个阈值很容易被误解,rss.server.flush.cold.storage.threshold.size 默认值是 64M;如果在 MEMORY_LOCALFILE_HDFS 模式下把它设置成 100g,那么即使启用了 HDFS,也不会有数据写入 DFS,因此使用 DFS 时需要设置合适值,例如 64m 或 128m。

六、功能特性

Uniffle 的优势可以从性能、可靠性、弹性和多引擎支持四个角度理解。主要包含 Fast、Reliable、Elastic 三个核心卖点:Fast 是减少 Shuffle 中的连接数和随机 I/O;Reliable 是减少大作业的 OOM 或磁盘空间失败;Elastic 是支持编排并提升资源利用率。在计算引擎支持方面,支持 Spark、MapReduce/Tez 和 Kubernetes Operator ,Uniffle 为 Spark 和 MapReduce 提供可插拔客户端插件。

|-------------------------|------------------------------------------------------------------|-------------------------|
| 能力 | 说明 | 工程价值 |
| 远程 Shuffle | Spark 应用可把 Shuffle 数据存到远程服务器 | 解耦计算节点与 Shuffle 数据 |
| Coordinator 分配 | Coordinator 收集 Server 状态并为作业分配 Server | 支持集中调度和负载均衡 |
| 多存储模式 | 支持 MEMORY_LOCALFILE、MEMORY_HDFS、MEMORY_LOCALFILE_HDFS | 可按成本、性能、可靠性选择存储 |
| 动态客户端配置 | Coordinator 可启用 dynamic client conf | 降低客户端逐作业配置成本 |
| Client Quorum | 客户端可把 block 写入多个 Server,读端从其中一个读取 | 提高 Shuffle Server 故障容忍度 |
| Adaptive Remote Shuffle | Spark 可通过 DelegationRssShuffleManager 智能选择内置 Shuffle 或远程 Shuffle | 支持灰度和按条件启用 |
| Kubernetes Operator | 扩展 Kubernetes API 管理 Uniffle 实例 | 适配云原生部署 |
| Spark Uniffle UI | 0.10.0 引入 Spark Uniffle UI | 改善观测和问题定位 |

Client Quorum 是一个值得关注的可靠性特性,该机制是客户端行为,Shuffle writer 会把每个 block 发送到多个 Server,Shuffle reader 可从其中一个 Server 获取 block 数据;由于多副本会降低 Shuffle 性能并增加资源消耗,因此它被设计为可选能力。

Adaptive Remote Shuffle 则更适合渐进式落地,Uniffle 支持 adaptive enabling,客户端使用 DelegationRssShuffleManager 并提供唯一 access_id,Coordinator 据此判断是否启用远程 Shuffle;当前该特性仅支持 Spark。

七、适用场景

Uniffle 最适合 Shuffle 成本高、稳定性要求高、计算资源生命周期不稳定或本地磁盘压力明显的场景。比如:大规模 Spark SQL、宽依赖聚合、Join、排序、窗口计算、超大 Shuffle ETL 作业,以及需要在 Kubernetes 或弹性 YARN 环境中频繁伸缩 Executor 的集群。

Uniffle 使用内存降低写阶段随机 I/O,如果 partition 太多,会把小数据刷到磁盘并影响性能,因此建议保证每个 partition 的内存大于 3MB,例如最繁忙时有 1k partitions,就应为它们提供 3GB 内存。Uniffle 偏 network-bound,使用 10Gbps 或更高网络是让这些应用更快的最佳方式。

不太适合优先引入 Uniffle 的场景也很明确:作业 Shuffle 很小、瓶颈在 CPU 或源端存储、集群网络较弱、团队暂时缺少独立运维 Shuffle 服务的能力。Uniffle 能降低 Shuffle 层的局部复杂度,但它本身也引入了 Coordinator、Shuffle Server、存储路径、指标监控、容量规划和版本兼容性管理。

八、部署使用

Uniffle 的部署通常分为三步:部署 Coordinator、部署 Shuffle Server、接入计算引擎客户端。

Coordinator 流程包括解压包到 RSS_HOME、配置 rss-env.sh、配置 coordinator.conf、配置 dynamic_client.conf和 exclude_nodes,最后通过 start-coordnator.sh 启动 Coordinator。

Shuffle Server 流程类似:解压包,配置 rss-env.sh,配置 server.conf,再通过 start-shuffle-server.sh 启动服务。Server 侧常见配置包括 RPC 端口、HTTP 端口、Coordinator quorum、本地存储路径、服务端 Buffer 容量、读取 Buffer 容量、心跳间隔、flush 线程池大小和存储类型。

复制代码
最小部署路径

1. 部署 Coordinator
   RSS_HOME
   ├── bin/rss-env.sh
   ├── conf/coordinator.conf
   ├── conf/dynamic_client.conf
   └── conf/exclude_nodes

2. 部署 Shuffle Server
   RSS_HOME
   ├── bin/rss-env.sh
   └── conf/server.conf

3. 接入 Spark
   SPARK_HOME/jars/
   └── rss-client-spark*-shaded.jar

4. 修改 Spark 配置
   spark.shuffle.manager
   spark.rss.coordinator.quorum

Spark 接入时,要把客户端 jar 加入 Spark classpath,例如SPARK_HOME/jars/;Spark2 jar 位于<RSS_HOME>/jars/client/spark2/,Spark3 jar 位于<RSS_HOME>/jars/client/spark3/。随后在 Spark 配置中启用 Uniffle,例如设置spark.shuffle.manager org.apache.spark.shuffle.RssShuffleManager和spark.rss.coordinator.quorum <coordinatorIp1>:19999,<coordinatorIp2>:19999。

复制代码
# Spark 接入 Uniffle 的基础配置示例
spark.shuffle.manager org.apache.spark.shuffle.RssShuffleManager
spark.rss.coordinator.quorum <coordinatorIp1>:19999,<coordinatorIp2>:19999

# Spark2 注意事项
# Spark2 不支持 AQE,因此应关闭:
spark.sql.adaptive.enabled false

如果要使用 Spark Dynamic Allocation,需要对 Spark 代码应用补丁,应用补丁并重新构建 Spark 后,需要设置 spark.shuffle.service.enabled false 和 spark.dynamicAllocation.enabled true;对于 Spark 3.5 或以上,还需要增加 spark.shuffle.sort.io.plugin.class org.apache.spark.shuffle.RssShuffleDataIo。

MapReduce 接入时,需要把 MR 客户端 jar 加入每个 NodeManager 的 classpath,并配置 mapreduce.rss.coordinator.quorum、RssMRAppMaster、RssMapOutputCollector 和 RssShuffle 等参数。另外,RssMRAppMaster 会自动关闭 slow start 和 job recovery。

九、配置与调优

Uniffle 调优首先看磁盘、内存、网络。HDD 或 SSD 都可以用于 Uniffle,但要提供足够数量的磁盘;通常 HDD 提供 100MB/s 写入速度,如果集群应用最繁忙时写入 1GB/s,就应该提供 10 块 HDD。

建议使用多个磁盘分摊 I/O 并提升总吞吐,RAID10 可获得较低延迟、高吞吐和较好容错,但会消耗双倍空间。 由于 Shuffle 数据通常是临时数据,如果成本敏感,可以优先从多块 HDD 加合理冗余开始,而不是直接堆高规格 SSD。

内存方面,Server 端的 rss.server.buffer.capacity 用于限制 Shuffle 数据 Buffer 占用的内存,但元数据内存不受该参数控制;建议在 Shuffle Server JVM 进程的 XMX_SIZE 中,在 rss.server.buffer.capacity + rss.server.read.buffer.capacity 之外预留一定冗余,例如 20%。

复制代码
Shuffle Server 内存预算示意

XMX_SIZE
+------------------------------------------------------+
| rss.server.buffer.capacity                           |
|   写入侧 Shuffle Buffer                              |
+------------------------------------------------------+
| rss.server.read.buffer.capacity                      |
|   读取侧 Buffer                                      |
+------------------------------------------------------+
| Metadata / JVM / 线程 / 其他对象                      |
|   建议预留冗余,例如 20%                          |
+------------------------------------------------------+

客户端写入方面,Spark Client 会为每个 partition 分配 Buffer;当总内存超过spark.rss.writer.buffer.spill.size时,客户端会把所有 Buffer 数据发送到 Shuffle Server,因此对于巨大 Shuffle 应用,建议增大该阈值以避免小网络 I/O。

十、小结

Apache Uniffle 的价值可以概括为一句话:把 Shuffle 从计算节点的本地状态中解耦出来,交给独立服务统一管理。它更适合大 Shuffle、多租户、弹性伸缩、云原生部署和本地磁盘压力明显的集群,而不是所有作业都必须引入的一层基础设施。

相关推荐
事界见闻1 小时前
汽车备件自动化立体仓库该如何规划?借鉴丰田 45000 个 SKU 项目实践
大数据·自动化·汽车
BYSJMG4 小时前
计算机毕设选题做什么好?基于大数据的用户健身行为数据分析与可视化系统,Hadoop+Spark处理
大数据·人工智能·hadoop·数据分析·spark·课程设计
知见漫记8 小时前
AI 桌面 Agent 本地执行能力技术对照:沙箱机制与权限模式拆解
大数据·人工智能
xcl092510 小时前
幼儿托育系统开发实战:从需求分析到上线全流程指南
java·大数据·需求分析
cspttty10 小时前
国际经济与贸易专业考什么证对就业有帮助
大数据
足球魅力10 小时前
哪款足球软件能分析凯利指数大数据?
大数据·业界资讯
xwz小王子11 小时前
机器人的“最后一毫米”: 新加坡南洋理工大学Facet-0如何教会基础模型“感受”自己的动作?
大数据·人工智能·机器人
腾视科技-AI11 小时前
腾视科技AIBOX双版本重磅发布!本地安全与全球适配,解锁视频智能新可能
大数据·人工智能·科技·安全·大模型·腾视科技·ai算力盒
姜穆澜11 小时前
OneID 从 0 到 1 完整生产案例(五)
大数据·notepad++