Spark on K8s 为什么需要 Remote Shuffle Service?Celeborn 解决了什么问题?

第 1269 题 约 4 分钟读完

技术点:Shuffle

本文目录
  1. 面试怎么回答
  2. 传统 Shuffle 的问题
  3. Remote Shuffle Service 的思路:存算分离
  4. Celeborn 是怎么做的
  5. 带来的收益和代价

面试怎么回答

传统 Shuffle 把数据写在 Executor 所在节点的本地盘,Executor 一退出下游就拉不到,只能重算;Reduce 端要做 M×R 次小块随机读;在 K8s 上又没有 YARN 那种 External Shuffle Service,动态资源分配只能靠 shuffleTracking,持有数据的 Executor 不敢释放,弹性很差。Remote Shuffle Service 把 Shuffle 数据交给独立的服务存,Map 端推送,服务端按 Reduce 分区合并成大文件,Reduce 端顺序读。Celeborn 是 Master + Worker 架构,支持副本和多层存储,Spark 只要换 ShuffleManager 就能接入。好处是 Executor 无状态、能放心弹性伸缩和用抢占式实例,大 Shuffle 更稳;代价是多一套集群要运维,多一跳网络,小作业不一定更快。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

很多公司把 Spark 从 YARN 迁到 Kubernetes 后,第一个踩的坑就是 Shuffle:动态资源分配用不顺、节点本地盘不够、Executor 一被驱逐就重算一大片。面试官问这题,想看你是否理解 Shuffle 数据「放在哪」对稳定性和弹性的影响。

传统 Shuffle 的问题

Spark 默认的 Shuffle 是「Map 端写本地盘,Reduce 端去各个节点拉」:

  1. 数据跟着 Executor 走:Shuffle 文件在 Executor 所在节点的本地盘上,Executor 一退出,下游就拉不到数据,只能重算上游 Stage;
  2. 动态资源分配受限:在 YARN 上靠 External Shuffle Service(NodeManager 里的常驻服务)托管 Shuffle 文件,Executor 才能放心释放;K8s 上没有这个常驻服务,只能开 shuffleTracking,结果是持有 Shuffle 数据的 Executor 迟迟不敢释放,弹性大打折扣;
  3. 随机小 IO:M 个 Map × R 个 Reduce,Reduce 端要发起 M×R 次小块读取,任务一多磁盘随机 IO 非常严重;
  4. 本地盘成为瓶颈:K8s 节点往往是云盘或者盘很小,大 Shuffle 容易把盘写满。

Remote Shuffle Service 的思路:存算分离

把 Shuffle 数据从计算节点上拿走,交给一组独立的 Shuffle 服务存:

  • Map 端不再写本地盘,而是把数据推送(push)到远端 Shuffle 节点;
  • 远端按 Reduce 分区把数据聚合在一起,同一个分区的数据写进同一个文件;
  • Reduce 端只要到少数几个节点顺序读自己的分区文件。

Celeborn 是怎么做的

Apache Celeborn(原阿里的 RSS,2024 年成为 Apache 顶级项目)是这类方案里用得最多的:

  • 架构:Master 负责管理 Worker 和分配分区位置,Worker 负责接收、合并、存储 Shuffle 数据,支持内存、本地盘、HDFS / 对象存储等多层存储;
  • Push + 合并:同一个 Reduce 分区的数据在 Worker 上合并成大文件,把 M×R 次随机读变成 R 次顺序读;
  • 高可用:可以开副本,同一份数据同时推给两个 Worker,一个 Worker 挂了不用重算;
  • 多引擎:Spark、Flink(批模式)、MapReduce 都有客户端;
  • 接入简单:Spark 端把 spark.shuffle.manager 换成 Celeborn 的 ShuffleManager,配上 Master 地址即可,作业代码不用改。

带来的收益和代价

  • 收益:Executor 变成无状态,可以放心开动态资源分配、用 Spot / 抢占式实例;大 Shuffle 作业更稳定,不再因为单节点丢失整体重算;本地盘需求大幅下降。
  • 代价:多了一套要运维的集群,Worker 的内存、磁盘、网络要单独规划;数据多一跳网络,小作业未必更快;Worker 本身也要做好扩缩容和故障演练。