大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)

本文涉及的产品
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)

点一下关注吧!!!非常感谢!!持续更新!!!

目前已经更新到了:

Hadoop(已更完)

HDFS(已更完)

MapReduce(已更完)

Hive(已更完)

Flume(已更完)

Sqoop(已更完)

Zookeeper(已更完)

HBase(已更完)

Redis (已更完)

Kafka(已更完)

Spark(正在更新!)

章节内容

上节我们完成了如下的内容:


SparkSQL 语句 编码 测试 结果

输入输出

数据源包含如Parquet、JSON、CSV、Avro、ORC、Hive、JDBC、ODBC

TextFile

978d49607804daac9defe320cc3c3326_43c0253ba04140ff8e645a5b62940261.png

SparkSQL中的Join

数据分析中将两个数据集进行Join操作是很常见的场景。在Spark的物理计划阶段,Spark的Join Selection类会根据Join Hints 策略,Join表的大小、Join是等值Join还是不等值以及参与Join的Key是否可以排序等条件来选择最终的Join策略,最后Spark会利用选择好的Join策略执行最终的计算。


当前Spark一共支持五种Join策略:


Broadcast hash join (BHJ)

Shuffle hash join (SHJ)

Shuffle sort merge join(SMJ)

Shuffle-and-replicate nested loop join,又叫笛卡尔积(Cartesian product join)

Broadcast nested loop join(BNLJ)

其中 BHJ 和 SMJ 这两种 Join 策略是我们运行 Spark 任务最常见的。

JoinSelection 会先根据 Join 的 Key 为等值Join来选择 Broadcast hash join、Shuffle hash join、Shuffle sort merge join中的一个。

如果Join的Key为不等值Join或者没有指定Join条件,则会选择Broadcast nested loop join 或 Shuffle-and-replicate nested loop join。

不同的Join策略在执行效率上差别很大,了解每种Join策略的执行过程和适用条件是很有必要的。


Broadcast Hash Join

Broadcast Hash Join 的实现是将小表的数据广播到Spark所有的Executor端,这个广播过程和我们自己去广播数据没有什么区别:


利用 Collect 算子将小表的数据从Executor端拉到Driver端

在Driver端调用sparkContext.broadcast广播到所有Executor端

在Executor端使用广播的数据与大表进行Join操作(实际上执行Map操作)

这种Join策略避免了Shuffle操作,一般而言,Broadcast Hash Join会比其他Join策略执行的要快。

02da68259927c633ffb15241499b113c_6e156d7a15b740519b9c102b99b86f2f.png 使用这种 Join 策略必须满足如下的条件:


小表的数据必须很小,可以通过 spark.sql.autoBroadcasetJoinThreshold 参数来配置,默认是10MB

如果内存比较大,可以将阈值适当加大

将 spark.sql.autoBroadcastJoinThreshold 参数设置为-1,可以关闭这种连接方式

只能用于等值Join,不要求参与Join的keys可排序

Shuffle Hash Join

当表中的数据比较大,又不适合使用广播,这个时候就可以考虑 Shuffle Hash Join。

Shuffle Hash Join 同样是在大表和小表进行Join的时候选择了一种策略。

它的计算思想是:把大表和小表按照相同的分区算法和分区数据进行分区(根据参与Join的Keys进行分区),这样保证了 Hash 值一样的数据都分发到同一个分区中,然后在同一个 Executor 中两张表 Hash 值一样的分区就可以在本地进行Hash Join了。在进行 Join 之前,还会对小表的分区构建 Hash Map,Shuffle Hash Join 利用了分治思想,把大问题拆解成小问题去解决。

79ee99e3f01c36be44f5b1940954a20f_44cd0864bf94472d9fe57382ab0df871.png 要启动 Shuffle Hash Join 必须满足以下条件:


仅支持等值 Join,不要求参与Join的Keys可排序

spark.sql.join.perferSortMergeJoin 参数必须设置值为 false,参数从Spark2.0版本引入,默认值是true,也就是默认情况下是 Sort Merge Join

小表的大小(plan.stats.sizeInBytes)必须小于(spark.sql.autoBroadcastJoinThreshold * spark.sql.shuffle.partitions(默认200))

而且小表大小(stats.sizeInBytes)的三倍必须小于等于大表的大小(stats.sizeInBytes),也就是(a.stats.sizeInBytes * 3 < b.stats.sizeInBytes)

Shuffle Sort Merge Join

前面两种Join策略对表的大小都有条件的,如果参与Join的表都很大,这时候就得考虑用 Shuffle Sort Merge Join了。

Shuffle Sort Merge Join 的实现事项:


将两张表按照 Join Key进行Shuffle,保证 Join Key值相同的记录会被分在相应的分区

对每个分区内的数据进行排序

排序后再对相应的分区内的记录进行连接

无论分区多大,Sort Merge Join都不用把一侧的数据全部加载到内存中,而是即用即丢。

因为两个序列都有序,从头遍历,碰到Key相同的就输出,如果不同,左边小取左边,反之就取右边。

这样大大提高了大数据量下的SQL Join的稳定性。

125171248c47af75b36b26074e00b4f5_b5998661fbe04aeea22b0b4187e02bff.png 要启用Shuffle Sort Merge Join必须满足以下条件:


仅支持等值 Join,并且要求参与 Join 的 Keys 可排序

Cartesian Product Join

如果Spark中两张参与Join的表没有指定连接条件,那么产生Cartesian Product Join,这个Join得到的结果其实就是两张表行数的乘积。


Broadcast Nested Loop Join

可以把 Broadcast Nested Loop Join的执行看做下面的计算:

for record_1 in relation_1:
  for record_2 in relation_2:
    # join condition is executed

可以看出 Broadcast Nested Loop Join 在某些情况会对某张表重复扫描多次,效率非常低。从名字可以看出,这种Join会根据相关条件对小表进行广播,以减少表的扫描次数。

Broadcast Nested Loop Join支持等值和不等值Join,支持所有的Join类型。


SQL解析过程

基本概念

SparkSQL 可以说Spark中的精华部分,原来基于RDD构建大数据计算任务,重新在向Dataset转移,原来基于 RDD 写的代码也在迁移。

使用 SparkSQL 编码的好处是非常大的,尤其是性能方面,有很大提升。SparkSQL 中各种内嵌的性能优化比写RDD遵循各种最佳实践更加靠谱。

尤其对于新手来说,比如先 Filter 再 Map,SparkSQL中会自动进行谓词下推,Spark SQL中会自动使用 Broadcast Join来广播小表,把 Shuffle Join转换为 Map Join等等。


SparkSQL对SQL语句的处理和关系型数据库类似,即词法/语法解析、绑定、优化、执行。SparkSQL会先将SQL语句解析成一棵树,然后使用规则(Rule)对Tree进行绑定、优化等处理过程。

SparkSQL由:Core、Catalyst、Hive、Hive-ThriftServer四部分构成:


Core:负责处理数据的输入和输出,如获取数据,查询结果输出成DataFrame等

Catalyst:负责处理整个查询过程,包括解析、绑定、优化等。

Hive:负责对Hive数据进行处理

Hive-ThriftServer:主要用于对Hive的访问

e862dc4f03d261cb0084154b3406ebf0_7306124de3fa46f5820bc1fd7a69912b.png

SparkSQL的代码复杂度是问题的本质复杂度带来说,SparkSQL中的Catalyst框架大部分逻辑是在一个Tree类型的数据结构上做各种折腾,基于Scala来实现还是很优雅的,Scala的偏函数和强大的Case正则匹配,让整个代码看起来非常优雅。


SparkSession是编写Spark应用代码的入口,启动一个spark-shell会提供给你创建spark-session,这个对象是整个Spark应用的起始点,以下是SparkSession的一些重要的变量和方法:

65f1a71e706b4a4582ffc9f87e75e963_50565cc8c4dd4670be3f2ff7d09a7792.png

编写代码

package icu.wzk
import org.apache.spark.sql.{DataFrame, SparkSession}



object TestDemo01 {
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .appName("TestDemo01")
      .master("local[*]")
      .getOrCreate()

    spark.sparkContext.setLogLevel("warn")
    import spark.implicits._

    Seq((0, "zhansan", 10),
      (1, "lisi", 11),
      (2, "wangwu", 12)).toDF("id", "name", "age")
      .createOrReplaceTempView("stu")

    Seq((0, "chinese", 80),
      (0, "math", 100),
      (0, "english", 98),
      (1, "chinese", 86),
      (1, "math", 97),
      (1, "english", 90),
      (2, "chinese", 90),
      (2, "math", 94),
      (2, "english", 88)).toDF("id", "subject", "score")
      .createOrReplaceTempView("score")

    val df: DataFrame = spark.sql(
      """
        |SELECT SUM(v) AS total_score, name
        |FROM (
        |  SELECT stu.id, 100 + 10 + score.score AS v, name
        |  FROM stu
        |  JOIN score ON stu.id = score.id
        |  WHERE stu.age >= 11
        |) tmp
        |GROUP BY name
        |""".stripMargin)

    df.show()

    // 打印执行计划
    println(df.queryExecution)
    println(df.queryExecution.optimizedPlan)

    spark.close()
  }

}

运行输出

执行代码可见控制台输出如下数据(我就不往服务器发了):

控制台的内容如下图所示:

+-----------+------+
|total_score|  name|
+-----------+------+
|        602|wangwu|
|        603|  lisi|
+-----------+------+

== Parsed Logical Plan ==
'Aggregate ['name], ['SUM('v) AS total_score#27, 'name]
+- 'SubqueryAlias `tmp`
   +- 'Project ['stu.id, ((100 + 10) + 'score.score) AS v#26, 'name]
      +- 'Filter ('stu.age >= 11)
         +- 'Join Inner, ('stu.id = 'score.id)
            :- 'UnresolvedRelation `stu`
            +- 'UnresolvedRelation `score`

== Analyzed Logical Plan ==
total_score: bigint, name: string
Aggregate [name#8], [sum(cast(v#26 as bigint)) AS total_score#27L, name#8]
+- SubqueryAlias `tmp`
   +- Project [id#7, ((100 + 10) + score#22) AS v#26, name#8]
      +- Filter (age#9 >= 11)
         +- Join Inner, (id#7 = id#20)
            :- SubqueryAlias `stu`
            :  +- Project [_1#3 AS id#7, _2#4 AS name#8, _3#5 AS age#9]
            :     +- LocalRelation [_1#3, _2#4, _3#5]
            +- SubqueryAlias `score`
               +- Project [_1#16 AS id#20, _2#17 AS subject#21, _3#18 AS score#22]
                  +- LocalRelation [_1#16, _2#17, _3#18]

== Optimized Logical Plan ==
Aggregate [name#8], [sum(cast(v#26 as bigint)) AS total_score#27L, name#8]
+- Project [(110 + score#22) AS v#26, name#8]
   +- Join Inner, (id#7 = id#20)
      :- LocalRelation [id#7, name#8]
      +- LocalRelation [id#20, score#22]

== Physical Plan ==
*(2) HashAggregate(keys=[name#8], functions=[sum(cast(v#26 as bigint))], output=[total_score#27L, name#8])
+- Exchange hashpartitioning(name#8, 200)
   +- *(1) HashAggregate(keys=[name#8], functions=[partial_sum(cast(v#26 as bigint))], output=[name#8, sum#38L])
      +- *(1) Project [(110 + score#22) AS v#26, name#8]
         +- *(1) BroadcastHashJoin [id#7], [id#20], Inner, BuildLeft
            :- BroadcastExchange HashedRelationBroadcastMode(List(cast(input[0, int, false] as bigint)))
            :  +- LocalTableScan [id#7, name#8]
            +- LocalTableScan [id#20, score#22]
Aggregate [name#8], [sum(cast(v#26 as bigint)) AS total_score#27L, name#8]
+- Project [(110 + score#22) AS v#26, name#8]
   +- Join Inner, (id#7 = id#20)
      :- LocalRelation [id#7, name#8]
      +- LocalRelation [id#20, score#22]

接下篇:https://developer.aliyun.com/article/1622630

目录
相关文章
|
1月前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
88 13
|
2月前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
96 1
|
8天前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
19天前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
74 14
|
3天前
|
SQL 分布式计算 Java
Spark SQL向量化执行引擎框架Gluten-Velox在AArch64使能和优化
本文摘自 Arm China的工程师顾煜祺关于“在 Arm 平台上使用 Native 算子库加速 Spark”的分享,主要内容包括以下四个部分: 1.技术背景 2.算子库构成 3.算子操作优化 4.未来工作
|
27天前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
102 1
|
2月前
|
存储 分布式计算 数据挖掘
数据架构 ODPS 是什么?
数据架构 ODPS 是什么?
572 7
|
2月前
|
存储 分布式计算 大数据
大数据 优化数据读取
【11月更文挑战第4天】
70 2
|
15天前
|
分布式计算 Shell MaxCompute
odps测试表及大量数据构建测试
odps测试表及大量数据构建测试
|
2月前
|
数据采集 监控 数据管理
数据治理之道:大数据平台的搭建与数据质量管理
【10月更文挑战第26天】随着信息技术的发展,数据成为企业核心资源。本文探讨大数据平台的搭建与数据质量管理,包括选择合适架构、数据处理与分析能力、数据质量标准与监控机制、数据清洗与校验及元数据管理,为企业数据治理提供参考。
123 1

推荐镜像

更多