本文整理自字节跳动基础架构的大数据开发工程师魏中佳在 ApacheCon Aisa 2022 「大数据」议题下的演讲,主要介绍 Cloud Shuffle Service(CSS) 在字节跳动 Spark 场景下的设计与实现。
作者|字节跳动基础架构大数据开发工程师-魏中佳
背景介绍
在大数据场景下,数据 Shuffle 表示了不同分区数据交换的过程,Shuffle 的性能往往会成为作业甚至整个集群的性能瓶颈。特别是在字节跳动每日上百 PB Shuffle 数据的场景下,Shuffle 过程暴露出来了很多问题,本文会逐个展开此类问题并介绍在字节跳动的优化实践。
External Shuffle Service
首先来看,在 Spark 3.0 及最新的 Spark 3.3 中,External Shuffle Service(以下简称 ESS)是如何完成 Shuffle 任务的?
如下图,每一个 Map Task,从 Mapper 1 到 Mapper M 都会在本地生成属于自己的 Shuffle 文件。这个 Shuffle 文件内部由 R 个连续的数据片段组成。每一个 Reduce Task 运行时都会分别连接所有的 Task,从 Mapper 1 一直到 Mapper M 。连接成功后,Reduce Task 会读取每个文件中属于自己的数据片段。
上述方式带来的问题是显而易见的:
-
由于每次读取的都是这个 Shuffle 文件的 1/R,通常情况下这个数据量是非常非常小的,大概是 KB 级别(从几百 KB 到几 KB 不等),这样会给磁盘(尤其是 HDD )带来大量随机的读请求。
-
同时,大家可以看到,Reduce 进行的 Shuffle Fetch 请求整体看是一个网状结构,也就是说会存在大量的网络请求,量级大概是 M 乘以 R,这个请求的数量级也是非常大的。
这两个问题随着作业规模的扩大,会带来越来越严重的 Shuffle Failure 问题。Shuffle Failure 意味着超时,Shuffle Failure 本身还有可能导致 Stage 重算,甚至导致作业失败,严重影响批式作业的稳定性,同时还会浪费大量的计算资源(因为 Fetch 等待超时的时候,CPU 是空闲的)。
Spark 在字节跳动的应用
在字节跳动内部,Spark 作业规模较大:
-
日均 100 万左右个作业
-
日均 300 PB Shuffle 数据
-
大量作业签署 SLA,对稳定性要求非常高,超时严重还会严重影响下游
-
大量 HDD 机器和少量 SSD 机器
-
大量在线业务低峰出让的资源,可用磁盘空间非常小,需要把存储拉远
下图是字节跳动内部一个 Spark 作业的 Shuffle Chunk Size 情况。这个作业只有 400 兆的 Shuffle 数据,但是它的 M 乘以 R 量级是 4 万乘 4 万。简单算一下,在这个例子中,平均的 Fetch Chunk 大小甚至远远小于 1K ,量级是非常非常小的。
再看一个混部集群中 Spark 作业的 Shuffle Fetch-Failure 的实时监控。下图监控中每个点的含义是——在这个时刻处于 Running 状态的 Application 的 Fetch-Failure 次数的总和。
上文提到,每一个 Fetch-Failure 都可能意味着一定时间的超时等待和计算资源空跑,同时还可能意味着触发 Stage 重算,甚至作业的失败。
所以,解决这个问题对于提升 Spark 的资源利用率和稳定性都具有重要意义。
问题总结
综上所述,ESS 在字节跳动业务场景下面临如下问题:
-
Chunk Size 过小导致磁盘产生大量随机 IO,降低磁盘的吞吐,引发 Chunk Fetch 请求的堆积、超时甚至引发 Stage Retry;
-
磁盘 IOPS 无法在操作系统层面进行隔离,Shuffle 过程中不同 Application 作业会互相影响;
-
在离线混部场景下,我们希望利用在线服务业务低峰期的 CPU,但缺少对应的磁盘资源。
External Shuffle Service 的优化
针对上述问题和需求,我们先对 ESS 进行了优化。
参数调优
首先是参数调优。为了实现参数调优,我们研发了一个旁路系统,如下图。
-
首先,采集 Spark、Yarn 运行时的 Event Log 作为数据源;
-
其次,使用 Flink 对原始数据进行 Join 和计算,得到作业某个 Stage 的 Shuffle 量、