目录
Executors
Environment
Storage
SQL
Exchange
Sort
Aggregate
Jobs
Stages
Stage DAG
Event Timeline
Task Metrics
Summary Metrics
Tasks
💐💐扫码关注公众号,回复 spark 关键字下载geekbang 原价 90 元 零基础入门 Spark 学习资料💐💐
Executors
为了叙述方便,我们以表格的形式说明这些 Metrics 的含义与作用。
Executors 页面清清楚楚地记录着每一个 Executor 消耗的数据量,以及它们对 CPU、内存与磁盘等硬件资源的消耗。基于这些信息,我们可以轻松判断不同 Executors 之间是否存在负载不均衡的情况,进而判断应用中是否存在数据倾斜的隐患。
Environment
就类别来说,它包含 5 大类环境信息,为了方便叙述,我把它们罗列到了下面的表格中。
这 5 类信息中,Spark Properties 是重点,其中记录着所有在运行时生效的 Spark 配置项设置。通过 Spark Properties,我们可以确认运行时的设置,与我们预期的设置是否一致,从而排除因配置项设置错误而导致的稳定性或是性能问题。
Storage
Storage 详情页,记录着每一个分布式缓存(RDD Cache、DataFrame Cache)的细节,包括缓存级别、已缓存的分区数、缓存比例、内存大小与磁盘大小。Spark 支持的不同缓存级别,它是存储介质(内存、磁盘)、存储形式(对象、序列化字节)与副本数量的排列组合。对于 DataFrame 来说,默认的级别是单副本的 Disk Memory Deserialized,如上图所示,也就是存储介质为内存加磁盘,存储形式为对象的单一副本存储方式。
Cached Partitions 与 Fraction Cached 分别记录着数据集成功缓存的分区数量,以及这些缓存的分区占所有分区的比例。当 Fraction Cached 小于 100% 的时候,说明分布式数据集并没有完全缓存到内存(或是磁盘),对于这种情况,我们要警惕缓存换入换出可能会带来的性能隐患。
后面的 Size in Memory 与 Size in Disk,则更加直观地展示了数据集缓存在内存与硬盘中的分布。从上图中可以看到,由于内存受限(3GB/Executor),摇号数据几乎全部被缓存到了磁盘,只有 584MB 的数据,缓存到了内存中。坦白地说,这样的缓存,对于数据集的重复访问,并没有带来实质上的性能收益。基于 Storage 页面提供的详细信息,我们可以有的放矢地设置与内存有关的配置项,如 spark.executor.memory、spark.memory.fraction、spark.memory.storageFraction,从而有针对性对 Storage Memory 进行调整。
SQL
当我们的应用包含 DataFrame、Dataset 或是 SQL 的时候,Spark UI 的 SQL 页面,就会展示相应的内容,如下图所示。
一级入口页面,以 Actions 为单位,记录着每个 Action 对应的 Spark SQL 执行计划。我们需要点击“Description”列中的超链接,才能进入到二级页面,去了解每个执行计划的详细信息。
点击图中的“save at:27”,即可进入到该作业的执行计划页面,如下图所示。
图中红色的部分为 Exchange,代表的是 Shuffle 操作,蓝色的部分为 Sort,也就是排序,而绿色的部分是 Aggregate,表示的是(局部与全局的)数据聚合,这三部分是硬件资源的主要消费者。
Exchange
下图中并列的两个 Exchange,对应的是示意图中 SortMergeJoin 之前的两个 Exchange。它们的作用是对申请编码数据与中签编码数据做 Shuffle,为数据关联做准备。
对于每一个 Exchange,Spark UI 都提供了丰富的 Metrics 来刻画 Shuffle 的计算过程。从 Shuffle Write 到 Shuffle Read,从数据量到处理时间,应有尽有。
为了让你获得直观感受,我还是举个例子说明。比方说,我们观察到过滤之后的中签编号数据大小不足 10MB(7.4MB),这时我们首先会想到,对于这样的大表 Join 小表,Spark SQL 选择了 SortMergeJoin 策略是不合理的。基于这样的判断,我们完全可以让 Spark SQL 选择 BroadcastHashJoin 策略来提供更好的执行性能。至于调优的具体方法,想必不用我多说,你也早已心领神会:要么用强制广播,要么利用 Spark 3.x 版本提供的 AQE 特性。
Sort
可以看到,“Peak memory total”和“Spill size total”这两个数值,足以指导我们更有针对性地去设置 spark.executor.memory、spark.memory.fraction、spark.memory.storageFraction,从而使得 Execution Memory 区域得到充分的保障。以上图为例,结合 18.8GB 的峰值消耗,以及 12.5GB 的磁盘溢出这两条信息,我们可以判断出,当前 3GB 的 Executor Memory 是远远不够的。那么我们自然要去调整上面的 3 个参数,来加速 Sort 的执行性能。
Aggregate
对于 Aggregate 操作,Spark UI 也记录着磁盘溢出与峰值消耗,即 Spill size 和 Peak memory total。这两个数值也为内存的调整提供了依据,以上图为例,零溢出与 3.2GB 的峰值消耗,证明当前 3GB 的 Executor Memory 设置,对于 Aggregate 计算来说是绰绰有余的。
Jobs
对于 Jobs 页面来说,Spark UI 也是以 Actions 为粒度,记录着每个 Action 对应作业的执行情况。我们想要了解作业详情,也必须通过“Description”页面提供的二级入口链接。在 Jobs 页面,Spark UI 会把数据的读取、访问与移动,也看作是一类“Actions”,比如图中 Job Id 为 0、1、3、4 的那些。这几个 Job,实际上都是在读取源数据(元数据与数据集本身)。
Stages
Stage DAG
tage DAG 仅仅是 SQL 页面完整 DAG 的一个子集
Event Timeline
Event Timeline,记录着分布式任务调度与执行的过程中,不同计算环节主要的时间花销。图中的每一个条带,都代表着一个分布式任务,条带由不同的颜色构成。其中不同颜色的矩形,代表不同环节的计算时间。
理想情况下,条带的大部分应该都是绿色的(如图中所示),也就是任务的时间消耗,大部分都是执行时间。不过,实际情况并不总是如此,比如,有些时候,蓝色的部分占比较多,或是橙色的部分占比较大。在这些情况下,我们就可以结合 Event Timeline,来判断作业是否存在调度开销过大、或是 Shuffle 负载过重的问题,从而有针对性地对不同环节做调优。比方说,如果条带中深蓝的部分(Scheduler Delay)很多,那就说明任务的调度开销很重。这个时候,我们就需要参考公式:D / P ~ M / C,来相应地调整 CPU、内存与并行度,从而减低任务的调度开销。其中,D 是数据集尺寸,P 为并行度,M 是 Executor 内存,而 C 是 Executor 的 CPU 核数。波浪线~ 表示的是,等式两边的数值,要在同一量级。再比如,如果条带中黄色(Shuffle Write Time)与橙色(Shuffle Read Time)的面积较大,就说明任务的 Shuffle 负载很重,这个时候,我们就需要考虑,有没有可能通过利用 Broadcast Join 来消除 Shuffle,从而缓解任务的 Shuffle 负担。
Task Metrics
Summary Metrics
对于这些详尽的 Task Metrics,难能可贵地,Spark UI 以最大最小(max、min)以及分位点(25% 分位、50% 分位、75% 分位)的方式,提供了不同 Metrics 的统计分布。这一点非常重要,原因在于,这些 Metrics 的统计分布,可以让我们非常清晰地量化任务的负载分布。换句话说,根据不同 Metrics 的统计分布信息,我们就可以轻而易举地判定,当前作业的不同任务之间,是相对均衡,还是存在严重的倾斜。如果判定计算负载存在倾斜,那么我们就要利用 AQE 的自动倾斜处理,去消除任务之间的不均衡,从而改善作业性能。
这里特别值得你关注的,是 Spill(Memory)和 Spill(Disk)这两个指标。Spill,也即溢出数据,它指的是因内存数据结构(PartitionedPairBuffer、AppendOnlyMap,等等)空间受限,而腾挪出去的数据。Spill(Memory)表示的是,这部分数据在内存中的存储大小,而 Spill(Disk)表示的是,这些数据在磁盘中的大小。因此,用 Spill(Memory)除以 Spill(Disk),就可以得到“数据膨胀系数”的近似值,我们把它记为 Explosion ratio。有了 Explosion ratio,对于一份存储在磁盘中的数据,我们就可以估算它在内存中的存储大小,从而准确地把握数据的内存消耗。
Tasks
每个 Task 都有自己的本地性倾向。结合本地性倾向,调度系统会把 Tasks 调度到合适的 Executors 或是计算节点,尽可能保证“数据不动、代码动”。