本文讨论了 Join Strategies、Join 中的提示以及 Spark 如何为任何类型的 Join 选择最佳 Join 策略。

Spark 5种Join策略:

  • Broadcast Hash Join(BHJ)
  • Shuffle Sort Merge Join(SMJ)
  • Shuffle Hash Join(SHJ)
  • Broadcast Nested Loop Join(BNLJ)
  • Shuffle Cartesian Product Join(CPJ)。

其中优先级为:BHJ SMJ SHJ BNLJ CPJ。其中Catalyst中JoinSelection选择具体Join策略,对sql进行优化。

Spark如何选择加入策略?

Spark 通过考虑以下因素来选择 Join 策略:

  1. Join类型
  2. Join Hints

spark指定yarnip spark指定hash join_数据

如上流程图所示,Spark根据Join类型和Join中的Hints选择Join策略。Spark 2.x 仅支持广播提示,而 Spark 3.x 支持流程图中提到的所有加入提示。

当 Join 两边都指定了提示时,Spark 按以下顺序选择提示:
1. BROADCAST hint
2. MERGE hint
3. SHUFFLE_HASH hint
4. SHUFFLE_REPLICATE_NL hint
5. 当指定了 BROADCAST hint或 SHUFFLE_HASH hint时, Spark 会根据连接类型和数据大小选择构建端,在部分特定的策略中可能不支持所有的Join策略,因此设置了hint也会存在失效情况。

具体 Spark Join Strategies

1. Broadcast Hash join

当其中一个data很小并且能放入内存时,它会被广播给所有的执行者,并且会进行一个 Hashoin。



spark.sql.autoBroadcastJoinThreshold 可以配置自动广播的数据集大小(以字节为单位)。
这里,spark.sql.autoBroadcastJoinThreshold=-1 将禁用广播连接,而默认 spark.sql.autoBroadcastJoinThreshold=10485760,即 10MB。


spark指定yarnip spark指定hash join_sql_02


在以下情况下,哪个表将被广播?

  1. 如果在连接的任一侧指定了广播提示,则无论 autoBroadcastJoinThreshold 如何,都将广播带有提示的连接端。
  2. 如果在 Join 的两侧都指定了广播提示,则将广播具有较小物理数据大小的一侧。
  3. 如果没有提示,并且表的物理大小 < autoBroadcastJoinThreshold,该表将被广播到所有executor。



spark指定yarnip spark指定hash join_数据_03


(Broadcast Hash Join)BHJ 可以比其他 Join 算法执行得更快,因为不涉及shuffle。

BHJ是对性能有好处吗?

广播表是网络密集型操作。当广播表很大时,可能会导致OOM或性能比其他算法差,被广播的表最大不能超过8G,否则会直接报错。

数据倾斜

当想要连接这两个表时,“Skewness”是开发人员面临的最常见问题。当 Join 键在数据集中分布不均匀时,Join 会出现倾斜。当 Join 倾斜时,Spark 无法并行执行操作,因为 Join 的负载将不均匀地分布在 Executor 之间。

如果一个table很小,可以决定直接广播!观察执行期间任务发生了什么:其中一项任务花费了更多时间。



spark指定yarnip spark指定hash join_spark指定yarnip_04


2. 随机散列连接

当表比较大时,使用广播可能会导driver和executor的内存问题。在这种情况下,将使用 Shuffle Hash Join。这是一个昂贵的连接,因为它涉及shuffle和repartition。此外,它需要内存和计算来维护哈希表。

Shuffle Hash Join分两步执行:

第 1 步 - shuffle:来自连接表的数据基于连接key进行分区。在分区之间打乱数据,以将记录的相同join key分配给相应的分区。

第 2 步 - hash join:对每个分区上的数据执行经典的单节点hash连接算法。



spark指定yarnip spark指定hash join_spark_05


如果要使用 Shuffle Hash Join,spark.sql.join.preferSortMergeJoin 需要设置为 false,构建 hash map 的成本比对数据排序要少,仅限key比较离散。但是Spark默认使用Sort-merge Join (优先于 Shuffle Hash Join)

当数据与您要加入的键均匀分布并且您有足够数量的键用于并行性时,Shuffle Hash Join 的性能是最佳的。

触发Shuffle Hash Join是有条件的,一般至少需要如下(即便满足也不一定能走,还需要看小表是否够小,能构建HashMap):

1)外表比内表至少大3倍

2)内表所有数据分片,都要小于广播变量(每个分区的平均大小不超过spark.sql.autoBroadcastJoinThreshold设定的值,即shuffle read阶段每个分区来自buildIter的记录要能放到内存中)

3. Shuffle sort-merge Join

Shuffle Sort-merge Join(SMJ)涉及对数据进行shuffle以获得相同的Join key与相同的exector,然后在executor节点的分区级别执行Sort-merge Join操作。分区在 Join 操作之前按 Join 键排序。

它有3个阶段:

  1. shuffle阶段:两个大表将根据集群中分区的连接键重新分区。
  2. sort阶段:对每个分区内的数据进行并行排序。
  3. merge阶段:加入排序和分区的数据。它通过迭代元素并连接具有相同 Join 键值的行来合并数据集。


spark指定yarnip spark指定hash join_数据_06


SMJ 在大多数情况下都比其他JOIN执行得更好,并且具有非常可扩展的方法,因为它消除了HASH的开销并且不需要整个数据都装进内存(落地磁盘,归并排序)。

4.Broadcast Nested Loop Join

Broadcast Nested Loop Join在未超过broadcast 阈值时选择。它同时支持等值连接和非等值连接。它还支持所有其他 Join 类型,但在以下情况下优化了实现:

  1. 左侧在右侧外连接中广播。
  2. 右侧广播在左外,左半和左反加入。
  3. 在一个类似内部的Join。

在其他情况下,我们需要多次扫描数据,这可能会相当慢。


spark指定yarnip spark指定hash join_spark指定yarnip_07


5.Cartesian Join

当 Join 类型为 inner like 且不存在 Join 键时,将选择 Cartesian Join。Cartesian Join计算 2 个表的笛卡尔积。如果我们想使用笛卡尔连接,我们必须在 Spark Session创建时设置 spark.sql.crossJoin.enabled=true 或为 Spark-shell 设置它: spark-shell — conf spark.sql.crossJoin.enabled= true,否则 Spark 将抛出 AnalysisException。



spark指定yarnip spark指定hash join_数据_08

Spark不同JOIN支持的JOIN策略 



spark指定yarnip spark指定hash join_sql_09


结论

即使 Apache Spark 中的联接在内部选择了最佳联接算法,开发人员也可以使用提示来更改此决定。在不了解数据性质的情况下在 Join Hints可能会导致 OOM 。

如果开发者熟悉底层数据并且没有在 Join 中设置Hints,就直接走Spark默认优化策略。