每天认识一个组件:统一 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,用于后续数据校验。

arduino 复制代码
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 元信息定位数据。

sql 复制代码
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_LOCALFILEMEMORY_HDFSMEMORY_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.confexclude_nodes,最后通过 start-coordnator.sh 启动 Coordinator。

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

bash 复制代码
最小部署路径

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.RssShuffleManagerspark.rss.coordinator.quorum <coordinatorIp1>:19999,<coordinatorIp2>:19999

bash 复制代码
# 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 falsespark.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.quorumRssMRAppMasterRssMapOutputCollectorRssShuffle 等参数。另外,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%。

lua 复制代码
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 小时前
晶捷智能:集团型制造企业ERP如何实现多工厂统一管控
大数据·人工智能·python·制造
小岐AI观1 小时前
智赋岐黄能否嵌入机构现有业务体系?
大数据·人工智能·四诊仪·智赋岐黄·中医ai
和裕1 小时前
光伏储能定制加强型瓦楞纸箱:降低组件运输隐裂率与售后损耗的核心价值
大数据·运维·网络·人工智能·算法
4SAPI2 小时前
GPT-6 Astra 发布:AGI 时代是否到来,或许不是当前最重要的问题
大数据·人工智能·gpt·agi
dalong102 小时前
WPF:MVVM 示例
大数据·wpf
小柯南敲键盘2 小时前
跨境电商翻译工具,AI批量图片及视频字幕翻译
大数据·人工智能·python·音视频
大大大大晴天️3 小时前
用 Helm、CRD 与 Operator 管大数据:声明式运维如何替代脚本化部署
大数据·云原生
数造科技3 小时前
政务数据治理实战:统一数据底座如何破解商事注册“重复填报”与数据孤岛
大数据·人工智能·科技·政务·ai-native
金立基包装胶水4 小时前
纸袋热封胶常见都有哪些问题?
大数据·笔记·其他