Spark on K8s 为什么需要 Remote Shuffle Service?Celeborn 解决了什么问题?
技术点:Shuffle
面试怎么回答
传统 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 端去各个节点拉」:
- 数据跟着 Executor 走:Shuffle 文件在 Executor 所在节点的本地盘上,Executor 一退出,下游就拉不到数据,只能重算上游 Stage;
- 动态资源分配受限:在 YARN 上靠 External Shuffle Service(NodeManager 里的常驻服务)托管 Shuffle 文件,Executor 才能放心释放;K8s 上没有这个常驻服务,只能开
shuffleTracking,结果是持有 Shuffle 数据的 Executor 迟迟不敢释放,弹性大打折扣; - 随机小 IO:M 个 Map × R 个 Reduce,Reduce 端要发起 M×R 次小块读取,任务一多磁盘随机 IO 非常严重;
- 本地盘成为瓶颈: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 本身也要做好扩缩容和故障演练。