Spark 性能调优实战
吴磊
前 FreeWheel 机器学习团队负责人
8808 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 36 讲
Spark 性能调优实战
15
15
1.0x
00:00/00:00
登录|注册

26 | Join Hints指南:不同场景下,如何选择Join策略?

你好,我是吴磊。
在数据分析领域,数据关联可以说是最常见的计算场景了。因为使用的频率很高,所以 Spark 为我们准备了非常丰富的关联形式,包括 Inner Join、Left Join、Right Join、Anti Join、Semi Join 等等。
搞懂不同关联形式的区别与作用,可以让我们快速地实现业务逻辑。不过,这只是基础,要想提高数据关联场景下 Spark 应用的执行性能,更为关键的是我们要能够深入理解 Join 的实现原理。
所以今天这一讲,我们先来说说,单机环境中 Join 都有哪几种实现方式,它们的优劣势分别是什么。理解了这些实现方式,我们再结合它们一起探讨,分布式计算环境中 Spark 都支持哪些 Join 策略。对于不同的 Join 策略,Spark 是怎么做取舍的。

Join 的实现方式详解

到目前为止,数据关联总共有 3 种 Join 实现方式。按照出现的时间顺序,分别是嵌套循环连接(NLJ,Nested Loop Join )、排序归并连接(SMJ,Shuffle Sort Merge Join)和哈希连接(HJ,Hash Join)。接下来,我们就借助一个数据关联的场景,来分别说一说这 3 种 Join 实现方式的工作原理。
假设,现在有事实表 orders 和维度表 users。其中,users 表存储用户属性信息,orders 记录着用户的每一笔交易。两张表的 Schema 如下:
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了Spark中数据关联的实现方式和策略选择。首先介绍了三种Join实现方式:嵌套循环连接(NLJ)、排序归并连接(SMJ)和哈希连接(HJ),并分析了它们的工作原理和优劣势。在分布式环境中,数据关联依然遵循这三种实现方式,增加了网络分发这一变数。文章进一步讨论了在不同的数据关联场景中,Spark如何选择Join策略,包括等值Join和不等值Join的情况。此外,还介绍了开发者可以通过Join Hints指定Join策略,以满足特定的业务需求。总的来说,本文对于数据分析领域的技术人员来说是一份详实的指南,能够帮助他们更好地理解和应用Join策略,从而提高数据关联场景下Spark应用的执行性能。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Spark 性能调优实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(18)

  • 最新
  • 精选
  • louis
    SHJ的第二个条件,“内表数据分片的平均大小要小于广播变量阈值”。 这里为什么是广播变量阈值,这里不涉及广播啊?不应该是内表的每一个数据分片都恰好放入执行内存中。

    作者回复: 非常好的问题~ 同意你的说法,更严谨,确实是应该每个分片都能放入内存,才是更严谨的要求。 不过,这种说法的可操作性不强,因为我们很难确认,每个数据分片的大小,更难确认的是,每个分片都能刚好放进内存。 “内表数据分片的平均大小要小于广播变量阈值”,这种说法,实际上是一种“不准确”但是操作性更强的办法,计算分片的平均大小还是比较容易的,再者广播变量阈值其实是个很好的“参照系”。如果分片平均大小能够比广播阈值还要小,那么大概率SHJ能够顺利地执行成功。 你说的对,这里跟广播没有任何关系,跟广播阈值作对比,更多地是给大家一个明确的“参照系”。为什么选它呢?因为,在大多数情况下,咱们都会在广播阈值上“精打细算”,对它的设置都会比较谨慎,所以拿他做参照,会比较稳妥一些。 不过,话说回来,严格来说,就是应该按照你说的,确保所有分片,可以放进内存。

    2021-08-11
    2
    10
  • 快跑
    老师你好,产生了几个疑问,请帮忙看看我理解的对么 1、从MapReduce阶段来看JOIN,带Shuffle的Join应该都发生在Reduce阶段? 2、经过Shuffle后的数据不就已经是排序的么,这样子使用SMJ是不是比SHJ少了Hash计算,也减少构建Hash的内存开销。 3、不等值连接的情况,BNLJ不生效的时候,采用CPJ策略时候,JOIN发生在Reduce阶段? 这个时候数据不都分散在各个节点么。 其中一个节点的数据怎么跟其他节点的数据比较呢。 这个时候不仅仅是数据逻辑上要Nested Loop。就连数据也需要通过网络挨个节点传输一遍么

    作者回复: 好问题,一个个来说。 先说第一个,是的,Join的操作是发生在Reduce阶段,原因其实很简单,就是两张表Join Keys相同的records,要分发到同一个Executors进程,这样才能完成关联计算。一旦两表的records进入到同一个Executors,这个时候就是看采用什么实现机制了,也就是hash join,sort merge join,还是nested loop join。 第二个, 其实是反过来的,就是如果Spark选择Shuffle SMJ策略,那么map阶段就会按照(partitionId,Join Key)来排序。但如果采用了Shuffle Hash Join的策略,Map阶段只需要对partitionId做排序就可以,不需要对Join Key做排序,计算复杂度还是会有所降低的。 第三个问题,超级好的问题,对于CPJ,它其实就不再是简单的Shuffle过程了,一般的Shuffle过程,是每份数据只分发一次。但对于CPJ,就像你说的,它是数据集的全网分发。理想情况下,一张表“待在原地、保持不动”,而另一张表的所有数据分片,都需要在全网每个Executors中全都分发一遍,这也是Cartesian Product Join这个名字的由来,就是笛卡尔积么,非常变态加夸张的计算复杂度。所以不到万不得已,要尽量离CPJ远远地~

    2021-05-22
    2
    10
  • kingcall
    回答: 问题1:个人觉得SMJ 就是用在两张大表上的关联才有意思啊,也就是事实表 Join 事实表,但是这里要求是等值关联,如果是不等值关联就只能CPJ 问题2:可以分情况讨论一下,但是肯定是可以实现的 1. != 这样的关联,Sort Merge Join 和 Hash Join 都是不划算的,但是是可以实现的。 2. 大于等于、小于等于 、大于 、小于 Sort Merge Join 还是有可取之处的,但是还是考虑到了排序的成本,但是这个地方有一个问题那就是我们的shuffle输出的数据的本身就是有序的啊,所以我觉得 Sort Merge Join 是可以的,Hash Join 就算了,其实可以看出来hash 只适合等值,这是取决于hash 本身的特点的。

    作者回复: 到位~ 1. Shuffle Sort Merge Join,对于“大表Join大表”来说,是最稳定的实现方式,不过关于“大表Join大表”,咱们还是有一些调优手段的,这块可以关注28、29讲哈~ 2. 确实,排序和构建hash table本身成了开销,在不等值的情况下,已经没有任何意义。

    2021-05-12
    7
  • To_Drill
    老师您好,有个疑问想请教下,如果选择了SMJ,那么在map端shuffle的输出文件中是按partitionID和key排序的,但是map端不应该只是局部数据的排序嘛,当reduce端拉取各个输出文件的时候还是会做一次全局排序(粒度为partition)的吧?如果是这样的话那么map端的排序只是加快了后续reduce端全局排序的效率,而不是map端排序了之后reduce端就不需要排序了是吧?

    作者回复: 对的,没错!

    2021-10-28
    5
  • zxk
    1. 可以对 Sort Merge Join 做一个变种,例如一个表排序,一个表不排序,不排序的表作为驱动表,排序的表可以通过二分查找等方式快速定位驱动表的 Join Key。 2. 也可以强行实现,但 Sort Merge Join 方面的排序就会变得毫无意义,同时 Sort Merge Join、Hash Join 的时间复杂度也并未降低,反而带来了额外的排序开销与内存开销。

    作者回复: 满分💯 第一题答得尤其好,这道题的初衷就是鼓励大家发散思维,大开脑洞,去思考更多的实现方式~ 你说的实现方式,我觉得蛮有意思,算法复杂度介于NLJ和SMJ之间,也就是O(M*logN),前置开销是一次排序,这种实现其实还是能适配一些场景的~ 666~ 第二题分析的很到位,强行用SMJ的时候,排序就成了“戴斗笠打雨伞”,多此一举。(其实我特别想说,脱了裤子放屁,不过不太文雅,哈哈)

    2021-05-12
    2
    4
  • 斯盖丸
    老师一般SQL的教程里都是join的优化方法之一是小表join大表,但我看Spark里你都是大表join小表,在大数据里谁join谁要紧吗,感觉好像无所谓?

    作者回复: 没有区别哈~ 小表Join大表,掉过来不就是大表Join小表么?其实不论谁Join谁,其实优化器都是会做区分的。不过通常来说,在形式上,我们把外表(驱动表)放左边,而内表(基表)放右边,所以专栏里面,在描述的时候,都是“大表Join小表”。

    2021-05-13
    6
    2
  • 老师,斯盖丸同学的大表join小表,在spark中,大表作为驱动表放在左边,那和放在右边有效率影响吗?还是不成文的规范,第二点,在hive中,小表是放在左边的,如果足够小的话,hive会自动把小表放进内存中,相当于广播变量了

    作者回复: 大小表放哪边其实没关系的,Spark SQL在优化阶段会自行判断。实际上两张表重要的是“大、小”,而不是“左、右”,对于Spark SQL优化引擎来说,大小重要,左右不重要。我们之所以区分左右,主要是为了叙述方便而已~

    2021-05-19
    1
  • Sansi
    1.两张事实表最好的等值连接方式就是smj,可以让map端输出的时候先进行排序,reduce拉取数据的时候就可以对两个表的多个数据流进行join操作 2可以强行使用smj和hj,但是这样并没有意义,因为最后join还是m*n的复杂度(当时如果是大于或者小于的连接方式,在进行连接的可以优化一下),而smj会带来额外的排序开销,hj要求内存能够放得下并且需要构建hash表

    作者回复: 没错,大表Join大表最稳定的方式,确实就是Shuffle Sort Merge Join。同时如果Map阶段计算按照(partitionId,join key)排好序的话,后续Reduce阶段可以直接做SMJ的计算,相当于前置条件已经满足。 第二题说的没错,排序、构建哈希表本身成了开销、多此一举~

    2021-05-12
    1
  • 七夏、
    老师您好,有个问题需要您答惑下: 关于spark sql 中的广播机制,spark是会将满足条件的原表广播还是过滤后的结果表呢? 比如 关联 A表 A的大小为210M 但是广播机制是以hdfs 200M大小为阈值的,这个时候是不会被广播的,那如果我在A表做了 where 条件后 过滤后的A表只有120M,这个时候会被广播么?

    作者回复: 在没有开启AQE的情况下,你说的这种情况,不会广播,因为210M的A表,超过了广播阈值。 但是,如果开启了AQE,自动Join策略调整,AQE会把原本的Shuffle Join转化为Broadcast Join,这部分细节,可以看前面AQE的那一讲(24讲)哈~

    2021-11-11
  • 天翼
    请教一个问题,主动扫描的作为外表,被动参与扫描的表叫做内表,主动与被动是怎么区分的呢?

    作者回复: 是的,主要是表大小,通常来说,大多数数据库引擎,都会把尺寸比较大的表,当作外表、或者说驱动表;而尺寸较小的表,当作内表、也叫基表。

    2021-05-24
    2
收起评论
显示
设置
留言
18
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部