在Apache Spark中紧固你的斜向连接的五个技巧

213 阅读11分钟

连接是典型数据处理程序中最基本的转换之一。一个连接运算符使得在两个输入数据集之间进行关联、丰富和过滤成为可能。这两个输入数据集通常被分类为左边的数据集和右边的数据集,基于它们相对于连接子句/操作符的位置。

基本上,Join在一个条件语句上工作,该语句包括一个基于左数据集的左键和右数据集的右键之间的比较的布尔表达。左键和右键通常被称为 "*连接键"。*布尔表达式是针对两个输入数据集的每一对记录进行评估的。基于评价表达式的布尔输出,条件语句包括一个选择子句,从这对记录中选择一个,或者选择形成这对记录的组合记录。

在倾斜的数据集上执行连接 。当数据集中的记录的连接键的分布偏向于一小部分键的时候,数据集就会被认为是偏斜的连接操作。例如,当数据集中80%的记录只贡献了20%的连接键。

偏斜的数据集对连接的影响:倾斜的数据集,如果处理不当,会导致在Join阶段出现散兵游勇(阅读此链接故事,了解更多关于散兵游勇的信息)。这将降低Spark作业的整体执行效率。另外,倾斜的数据集会导致某些执行器的内存超限,从而导致Spark作业的失败。因此,在涉及到大型倾斜数据集时,识别和解决基于Join的阶段是很重要的。

解决偏斜连接的技术。到目前为止,你一定看到了很多分散的文献来处理倾斜的连接,但大多数都强调了1或2种技术,并简单地描述了其中的细节和限制。考虑到这种分散的描述,这个特别的故事试图为你提供一个完整的、全面的清单,其中包括在各种可能的情况下处理倾斜连接的五个重要技术。

广播哈希连接(Broadcast Hash Joi****n

在 "广播哈希 "连接中,左边或右边的输入数据集都被广播给执行者。广播哈希 "连接对倾斜的输入数据集是免疫的。这是由于根据 "连接键 "进行分区的事实,在左边和右边的数据集上不是强制性的。在这里,其中一个数据集是广播式的,而另一个可以以适当的方式进行适当的分区,以实现任何规模的统一并行。

Spark根据连接类型和输入数据集的大小来选择 "广播哈希连接"。如果Join类型是有利的,并且要广播的数据集的大小仍然低于可配置的限制(spark.sql.autoBroadcastJoinThreshold (默认为10MB)),"广播散列式Join "将被选择用于执行Join。因此,如果你把'spark.sql.autoBroadcastJoinThreshold'的限制增加到一个更高的值,这样'广播哈希连接'才会被选择。

人们也可以在基于Join类型的输入数据集的SQL查询中使用广播提示,强制Spark使用'广播散列式连接',而不考虑'spark.sql.autoBroadcastJoinThreshold'的值。

因此,如果能负担得起执行器的内存,应该采用'广播哈希'连接,以便更快地执行倾斜连接。然而,在计划使用这种最快的方法时,需要考虑一些突出的问题。

  • 不适用于全外联接
  • 对于内联接,执行器内存应该至少容纳两个输入数据集中较小的一个。
  • 对于左联接、左反联接和左半联接,执行器的内存应该容纳右边的输入数据集,因为右边的数据集需要被广播。
  • 对于右、右反和右半联合,执行者的内存应能容纳左边的输入数据集,因为左边的需要被广播。
  • 根据广播数据集的大小,执行器的执行内存也有相当大的需求

迭代广播连接

迭代广播"技术是对 "广播哈希 "连接的改编,以便处理更大的倾斜数据集。在任何一个输入数据集不能被广播给执行者的情况下,它是有用的。这可能是由于执行器的内存限制而发生的。

为了处理这种情况,"迭代广播"技术将其中一个输入数据集(最好是较小的数据集)分解成一个或多个小块,从而确保所产生的每个小块都能被轻易广播。然后,这些较小的块被一个一个地与另一个未分解的输入数据集使用标准的 "广播哈希"连接起来。这些多个连接的输出最后用 "联合"运算器结合在一起,产生最终的输出。

将数据集分解成小块的方法之一,是在数据集的每条记录中,在新增加的列'chunkId'中,从所需的小块数量中随机分配一个数字。一旦这个新列准备好了,就启动一个for循环来迭代块数。对于每个迭代,首先在与当前迭代的块数相对应的'chunkId '列上过滤记录。在每个迭代中,过滤后的数据集,然后使用标准的 "广播哈希 "连接,与未中断的其他输入数据集连接,以获得部分连接输出。然后,部分连接的输出与之前部分连接的输出相结合。循环退出后,人们将得到两个原始数据集的连接操作的整体输出。这种技术如下图1所示。

图1:实现迭代广播哈希连接

然而,与 "广播哈希连接 "相比,"迭代广播连接 "只限于 "内连接"。它不能处理全外连接、左连接和右连接。然而,对于 "内部连接",它可以处理两个数据集的偏度。

加盐排序合并连接

在资源紧张的情况下,'排序合并'方法在处理连接方面非常稳健。在此基础上,当人们想要将一个大的偏斜数据集与一个小的非偏斜数据集连接起来,但执行器的内存受到限制时,盐化版的'排序合并'可以被非常有效地使用。

此外,盐化排序合并版本也可以用来执行较小的非偏斜数据集与较大的偏斜数据集的左键连接,这在广播哈希连接中是不可能的,即使较小的数据集可以被广播给执行者。然而,为了确保Spark选择排序合并连接,我们必须关闭 "广播哈希连接 "方法。这可以通过设置'spark.sql.autoBroadcastJoinThreshold'为-1来完成。

加盐排序合并 "连接的工作与 "迭代广播哈希 "连接类似。一个额外的列'盐键'被引入到一个倾斜的输入数据集中。之后,对于每条记录,从选定的盐键值范围中随机分配一个数字给'盐键'列。

在对倾斜的输入数据集加盐后,在选定的范围内对盐键值进行循环。对于在循环中迭代的每一个盐键值,首先对迭代的盐键值进行过滤,在过滤之后,将过滤后的盐键输入数据集与其他未加盐的输入数据集连接起来,产生一个部分连接输出。为了产生最终的联合输出,所有的部分联合输出都使用联合运算符组合在一起。

对于 "加盐排序合并 "的方法,也有一个替代方法。在这种情况下,对于在循环中迭代的每一个盐键值,第二个非倾斜的输入数据集通过在新的 "盐 "列中重复相同的值来丰富当前迭代的盐键值,以产生一个部分盐丰富的数据集。所有这些部分丰富的数据集使用 "联合 "运算符进行组合,产生第二个非倾斜数据集的组合盐丰富的数据集版本。在这之后,第一个倾斜的盐化数据集与第二个盐丰富的数据集相连接,产生最终的连接输出。这种方法如下图2所示。

图2:实现盐分排序的合并连接

然而,另一种替代方法也存在 "加盐排序合并 "的方法。在这种方法中,在用额外的 "盐键 "列对倾斜的输入数据集进行加盐处理后,在未加盐的非倾斜数据集中也引入了一个 "盐 "列。盐 "列包含一个固定值(在所有记录中),等于一个由早期选定范围内的盐键的所有值组成的数组。之后,这个数据集在 "盐 "列上被爆炸。然后,爆炸后的数据集与先前的盐化倾斜输入数据集相连接,并在 "盐 "和 "盐键 "相等的情况下附加连接条件,产生最终的连接输出。

盐分排序合并连接不能处理全外层连接。而且,它不能处理输入数据集的偏度。它只能处理左边数据集的偏斜,在左边连接类别中(外、半和反)。同样,它也只能在右连接类别中处理右数据集的偏斜。

AQE (高级查询执行)

AQE是一套运行时优化功能,现在从Spark 3.0开始默认启用。这套功能的主要特点之一是能够自动优化偏斜数据集的连接。

AQE通常对倾斜数据集和非倾斜数据集的 "排序合并连接 "进行优化。AQE在分类合并连接的分区步骤中操作,两个输入数据集首先根据相应的连接键进行分区。在分区过程中,MapTasks写入shuffle块后,Spark Execution Engine获得每个shuffle分区的大小统计。有了这些来自Spark执行引擎的统计信息,AQE可以与某些可配置的参数一起确定某些分区是否是倾斜的。如果发现某些分区是偏斜的,AQE会将这些分区分解成更小的分区。这种分解是由一组可配置参数控制的。从一个较大的倾斜分区中分解出来的较小的分区,然后与其他非倾斜的输入数据集的相应分区的副本连接。该过程如下图所示

图3:AQE处理倾斜的Join的方式

以下是影响AQE中倾斜连接优化功能的配置参数。

"spark.sql.adaptive.skewJoin.enabled"。这个布尔参数控制偏斜连接优化是否打开或关闭。默认值为true。

"spark.sql.adaptive.skewJoin.skewedPartitionFactor"。这个整数参数控制偏斜分区的解释。默认值是5。

"spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes"。这个以MB为单位的参数也控制了对倾斜分区的解释。默认值是256MB。

当(partition size >skewedPartitionFactor * median partition size)和(partition size >skewedPartitionThresholdInBytes)都为真时,一个分区被认为是倾斜的。

像'Broadcast Hash Join'和'Salted Sort Merge Join'的AQE不能处理'Full Outer Join'。同时,它也不能处理输入数据集的偏斜性。因此,就像 "加盐排序合并连接 "一样,AQE只能在左连接类别(外连接、半连接和反连接)中处理左边数据集的倾斜,在右连接类别中处理右边数据集的倾斜。

广播式地图分区连接

广播式地图分区连接 "是唯一的机制,可以在一个大的倾斜数据集和一个较小的非倾斜数据集之间固定一个倾斜的 "全外部连接"。在这种方法中,两个输入数据集中较小的数据集被广播给执行者,而联合逻辑被手动配置在'MapPartitions'转换中,它被调用到较大的非广播数据集上。

尽管 "广播MapPartitions Join "支持所有类型的连接,并且可以处理数据集中的任何一个或两个的偏移,唯一的限制是它需要执行器上的大量内存。较大的执行器内存需要用来广播一个较小的输入数据集,并支持中间的内存收集,以便手动提供Join。

我希望上面的博客已经给了你一个很好的视角来处理Spark应用程序中的倾斜的Join。有了这样的背景,我鼓励大家在Spark应用的Join阶段遇到散兵游勇或内存超限时,探索其中的一个选项。

Apache Spark Executor(软件) Joins(并发库) Memory(存储引擎) Merge(版本控制) Partition(数据库) Record(计算机科学) sql