Spark原理与实践 | 青训营笔记

202 阅读9分钟

image.png 这是我参与「第四届青训营 」笔记创作活动的的第5天

课程概述

01.大数据处理引擎Spark 介绍 02. sparkCore原理解析 03.SparkSQL原理解析 04.业界挑战与实践

1.大数据处理引擎Spark 介绍

1.1大数据处理技术栈

1663074076895.png

1.2常见的大数据处理链路

1.3开源大数据处理引擎

1.4什么是Spark?版本演进、Spark生态和特点

Spark是一个通用的大数据分析引擎

1.5 Spark特点:多语言、丰富数据源、

1.6 Spark运行架构&部署方式

Spark运行架构和工作原理

Spark应用在集群上运行时,包括了多个独立的进程,这些进程之间通过驱动程序(Driver Program)中的SparkContext对象进行协调,SparkContext对象能够与多种集群资源管理器(Cluster Manager)通信,一旦与集群资源管理器连接,Spark会为该应用在各个集群节点上申请执行器(Executor),用于执行计算任务和存储数据。Spark将应用程序代码发送给所申请到的执行器,SparkContext对象将分割出的任务(Task)发送给各个执行器去运行。

需要注意的是

  • 每个Spark application都有其对应的多个executor进程。Executor进程在整个应用程序生命周期内,都保持运行状态,并以多线程方式执行任务。这样做的好处是,Executor进程可以隔离每个Spark应用。从调度角度来看,每个driver可以独立调度本应用程序的内部任务。从executor角度来看,不同Spark应用对应的任务将会在不同的JVM中运行。然而这样的架构也有缺点,多个Spark应用程序之间无法共享数据,除非把数据写到外部存储结构中。
  • Spark对底层的集群管理器一无所知,只要Spark能够申请到executor进程,能与之通信即可。这种实现方式可以使Spark比较容易的在多种集群管理器上运行,例如Mesos、Yarn、Kubernetes。
  • Driver Program在整个生命周期内必须监听并接受其对应的各个executor的连接请求,因此driver program必须能够被所有worker节点访问到。
  • 因为集群上的任务是由driver来调度的,driver应该和worker节点距离近一些,最好在同一个本地局域网中,如果需要远程对集群发起请求,最好还是在driver节点上启动RPC服务响应这些远程请求,同时把driver本身放在离集群Worker节点比较近的机器上。

1.7 Spark下载编译

1.8 Spark包概览

1.9 Spark提交命令

环境变量、spark-shell、spark-sql、pyspark

1.10 提交一个简单的任务

1.11 Spark UI

1.12 Spark性能benchmark

TPC-DS/TPC-H测试集

2. sparkCore原理解析

2.1 SparkCore

Spark从输入到结果输出中全部的数据结构都是基于RDD

SparkCore

RDD算子:对任何函数进行某一项操作都可以认为是一个算子,RDD算子是RDD的成员函数

Transform(转换)算子: 根据已有RDD创建新的RDD

Action(动作)算子: 将在数据集上运行计算后的数值返回到驱动程序,从而触发真正的计算

DAG(Directed Acyclic Graph): 有向无环图,Spark中的RDD通过一系列的转换算子操作和行动算子操作形成了一个DAG

DAGScheduler:将作业的DAG划分成不同的Stage,每个Stage都是TaskSet任务集合,并以TaskSet为单位提交给TaskScheduler。

TaskScheduler:通过TaskSetManager管理Task,并通过集群中的资源管理器(Standalone模式下是Master,Yarn模式下是ResourceManager)把Task发给集群中Worker的Executor

Shuffle:Spark中数据重分发的一种机制。

2.2 什么是RDD?

RDD(Resilient Distributed Dataset):弹性分布式数据集,是一个容错的、并行的数据结构。RDD提供了一个抽象的数据结构,我们不必担心底层数据的分布式特性,只需将具体的应用逻辑表达为一系列转换处理,不同RDD之间的转换操作形成依赖关系,可以实现管道化,从而避免了中间结果的存储,大大降低了数据复制、磁盘IO和序列化开销。

2.2.1 如何创建RDD?

RDD是只读的记录分区的集合,不能直接修改,只能基于稳定的物理存储中的数据集来创建RDD,或者通过在其他RDD上执行确定的转化操作(如map、join、groupBy等)而创建得到新的RDD。

创建RDD的三种方式

  • 1.集合(内存)方式
  • 2.本地文件
  • 3.HDFS文件
  • 4.从其他 RDD 创建
  • 5.直接创建 RDD(new)

2.2.1.1 集合(内存)方式创建RDD

主要提供了两种方式: parallelizemakeRDD(从底层代码实现来讲makeRDD就是parallelize)

scala版代码

import org.apache.spark.{SparkConf, SparkContext}

object Spark01_RDD_Memory {
  def main(args: Array[String]): Unit = {

    //准备环境
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")//[*]当前系统的最大可用核数
    val sparkContext = new SparkContext(sparkConf)
    //从内存中创建RDD,将内存中集合的数据作为处理的数据源
    val rdd1 = sparkContext.parallelize(//parallelize:并行
      List(1,2,3,4)
    )
    val rdd2 = sparkContext.makeRDD(
      List(1,2,3,4)
    )
    rdd1.collect().foreach(println)
    rdd2.collect().foreach(println)
    //关闭环境
    sparkContext.stop()

  }
}

2.2.1.2 本地文件方式创建RDD

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Spark02_RDD_File {
  def main(args: Array[String]): Unit = {

    //准备环境
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")//[*]当前系统的最大可用核数
    val sparkContext = new SparkContext(sparkConf)
    //从文件中创建RDD,将文件中的数据作为处理的数据源
    //path路径默认以当前的根路径为基准,可以写绝对路径,也可以写相对路径
    val fileRDD: RDD[String] = sparkContext.textFile("D:\SparkDemo\datas\1.txt")
    fileRDD.collect().foreach(println)
    //关闭环境
    sparkContext.stop()

  }
}

2.2.1.3 外部文件方式(HDFS\HBase等)创建RDD

object Spark02_RDD_File {
  def main(args: Array[String]): Unit = {

    //准备环境
    val sparkConf = new SparkConf().setMaster("local[*]").setAppName("RDD")//[*]当前系统的最大可用核数
    val sparkContext = new SparkContext(sparkConf)
    //从文件中创建RDD,将文件中的数据作为处理的数据源
    //path路径默认以当前的根路径为基准,可以写绝对路径,也可以写相对路径
    //val fileRDD: RDD[String] = sparkContext.textFile("D:\SparkDemo\datas\1.txt")
    //如果要用hive中的文件,一定要开hive
    //有时候会出现Name node is in safe mode---解决办法:hdfs dfsadmin -safemode leave
    //textFile: 以行为单位来读取数据,读取的数据都是字符串
    //wholeTextFiles: 以文件为单位读取数据
    val fileRDD: RDD[String] = sparkContext.textFile("hdfs://hadoop102:8020/user/hive/warehouse/student4/student.txt")
    fileRDD.collect().foreach(println)
    //关闭环境
    sparkContext.stop()

  }
}

2.2.1.4 从其他 RDD 创建

主要是通过一个 RDD 运算完后,再产生新的 RDD。

2.2.1.5 直接创建 RDD(new)

使用 new 的方式直接构造 RDD,一般由 Spark 框架自身使用

2.2.2 RDD算子

RDD采用了惰性调用,即在RDD的执行过程中,真正的计算发生在RDD的“行动”操作,对于“行动”之前的所有“转化”操作,Spark只是记录下“转化”操作应用的一些基础数据集以及RDD生成轨迹,即相互之间的依赖关系,而不会触发真正的计算。

2.2.3 RDD依赖(描述父子RDD之间的依赖关系)

窄依赖:map():父子RDD的每个partition至多对应一个子RDD的分区 宽依赖groupby():会产生Shuffle,父RDD的每个partition都可能对应多个RDD自取

2.2.4 RDD执行流程

RDD执行过程

划分Stage的整体思路:从后往前推,遇到宽依赖就断开,划分为一个Stage。遇到窄依赖,就将这个RDD加入该Stage中,DAG最后一个阶段会为每个结果的Partition生成一个ResultTask。每个Stage里面的Task数量由最后一个RDD的Partition数量决定,其余的阶段会生成ShuffleMapTask。

当RDD对象创建后,SparkContext会根据RDD对象构建DAG有向无环图,然后将Task提交给DAGScheduler。DAGScheduler根据ShuffleDependency将DAG划分为不同的Stage,为每个Stage生成TaskSet任务集合,并以TaskSet为单位提交给TaskScheduler。TaskScheduler根据调度算法(FIFO/FAIR)对多个TaskSet进行调度,并通过集群中的资源管理器(Standalone模式下是Master,Yarn模式下是ResourceManager)把Task调度(locality)到集群中Worker的Executor,Executor由SchedulerBackend提供。

2.3 调度器

2.4 内存机制

2.4.1 多任务间内存分配

2.5 Shuffle

2.5.1 SortShuffleManager

每个MapTask生成一个Shuffle数据文件和一个index文件

2.5.2 External Shuffle Service

3.SparkSQL原理解析

什么是SparkSQL?

SparkSQL:

目标:了解SQL执行链路。重点学习核心模块calalyst优化器以及SparkSQL三大重点特性(Codegen/AQE/RuntimeFilter)

SparkSQL执行过程

  • SQL Parse: 将SparkSQL字符串或DataFrame解析为一个抽象语法树/AST,即Unresolved Logical Plan
  • Analysis:遍历整个AST,并对AST上的每个节点进行数据类型的绑定以及函数绑定,然后根据元数据信息Catalog对数据表中的字段进行解析。 利用Catalog信息将Unresolved Logical Plan解析成Analyzed Logical plan
  • Logical Optimization:该模块是Catalyst的核心,主要分为RBO和CBO两种优化策略,其中RBO是基于规则优化,CBO是基于代价优化。 利用一些规则将Analyzed Logical plan解析成Optimized Logic plan
  • Physical Planning: Logical plan是不能被spark执行的,这个过程是把Logic plan转换为多个Physical plans
  • CostModel: 主要根据过去的性能统计数据,选择最佳的物理执行计划(Selected Physical Plan)。
  • Code Generation: sql逻辑生成Java字节码

影响SparkSQL性能两大技术:

  1. Optimizer:执行计划的优化,目标是找出最优的执行计划
  1. Runtime:运行时优化,目标是在既定的执行计划下尽可能快的执行完毕。

3.1 Catalyst优化器 - RBO

3.1 Catalyst优化器 - CBO

3.2 Adaptive Query Execution(AQE)

AQE - Coalescing Shuffle Partitions

AQE - Switching Join Strategies

AQE - Optimizing Skew Joins

AQE对于整体的Spark SQL的执行过程做了相应的调整和优化,它最大的亮点是可以根据已经完成的计划结点真实且精确的执行统计结果来不停的反馈并重新优化剩下的执行计划。

AQE框架三种优化场景:

  • 动态合并shuffle分区(Dynamically coalescing shuffle partitions)
  • 动态调整Join策略(Dynamically switching join strategies)
  • 动态优化数据倾斜Join(Dynamically optimizing skew joins)

3.3 Runtime Filter

3.4 Bloom Runtime Filter

3.5 Codegen - Expression

Codegen - WholeStageCodegen

4.业界挑战与实践

4.1 Shuffle稳定性问题

Shuffle稳定性解决方案

![GRM2)G{4L()B(]A{)$T_13P.png](p1-juejin.byteimg.com/tos-cn-i-k3…?)

4.2 SQL执行性能问题

SQL执行性能业界引擎实践

SQL执行性能解决方向

4.3参数推荐/作业诊断

课后

  1. 大数据的基础链路是?
  1. Spark RDD是如何执行的?如何划分stage?
  1. Spark内存是如何管理的?有什么特别机制?
  1. Spark Shuffle是怎么产生的?基本流程是?
  1. SparkSQL执行流程有哪些步骤?每个步骤的左右是?
  1. Runtime优化的方式都有哪些?
  1. Spark业界面临的问题是如何产生的?都有什么优化方向?