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

202 阅读5分钟

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

Spark原理与实践

一、大数据处理引擎Spark介绍

1.1 大数据处理技术栈

image.png

1.2 常见大数据处理链路

image.png

1.3 开源大数据处理引擎

image.png

1.4 什么是spark?

spark是一个用来实现快速,通用的集群计算平台 spark适用于各种各样原先需要多种不同的分布式平台的场景,包括批处理,迭代算法,交互式查询,流处理。通过在一个统一的框架下支持这些不同的计算,spark使我们可以简单而低耗地把各种处理流程整合在一起。

1.5 Spark版本演进

image.png

1.6 Spark生态和特点

image.png

特点:

  • 多语言支持
  • 丰富数据源
  • 丰富的API/算子

1.7 Spark运行架构&部署方式

image.png

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

二、SparkCore原理解析

image.png

2.1 RDD

2.1.1 RDD概念

弹性分布式数据集,是分布式内存的一个抽象概念

2.2.2 RDD依赖

image.png RDD依赖:描述父子RDD之间的依赖关系(lineage)

➢窄依赖:父RDD的每个partition至多对应一个子RDD分区。

  • NarrowDependency def getParents(partitionld: Int): Seq[Int]
  • One' ToOneDependency override def getParents(partitionld: Int): List[Int] = List(partitionld)
  • RangeDependency override def getParents(partitionld: Int): List[nt] = if (partitionld >= outStart && partitionld < outStart + length) List(partitionld - outStart + inStart)
  • PruneDependency ➢宽依赖:父RDD的每个partition都可能对应多个子RDD分区。
  • ShuffleDependency

RDD执行流程

image.png

2.3 Scheduler(调度器)

image.png

2.4 Memory Management

image.png

  • Executor内存主要有两类: Storage、 Execution
  • UnifiedMemoryManager统一管理Storage/Execution内存
  • Storage和Execution内存使用是动态调整,可以相互借用
  • 当Storage空闲,Execution 可以借用Storage的内存使用,
  • 可以减少spill等操作,Execution 使用的内存不能被Storage驱逐
  • 当Execution空闲,Storage 可以借用Execution的内存使用,
  • 当Execution需要内存时,可以驱逐被Storage借用的内存,直到
  • spark.memory.storageFraction边界

三、SparkSQL原理解析

image.png

image.png 影响SparkSQL性能两大技术:

  1. Optimizer:执行计划的优化,目标是找出最优的执行计划

  2. Runtime:运行时优化,目标是在既定的执行计划下尽可能快的执行完毕

Catalyst优化

  1. Rule Based Optimizer(RBO): 基于规则优化,对语法树进行一次遍历,模式匹配能够满足特定规则的节点,再进行相应的等价转换。

  2. Cost Based Optimizer(CBO): 基于代价优化,根据优化规则对关系表达式进行转换,生成多个执行计划,然后CBO会通过根据统计信息(Statistics)和代价模型(Cost Model)计算各种可能执行计划的代价,从中选用COST最低的执行方案,作为实际运行方案。CBO依赖数据库对象的统计信息,统计信息的准确与否会影响CBO做出最优的选择。

AQE

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

AQE框架三种优化场景:

  • 动态合并shuffle分区(Dynamically coalescing shuffle partitions)

  • 动态调整Join策略(Dynamically switching join strategies)

  • 动态优化数据倾斜Join(Dynamically optimizing skew joins)

RuntimeFilter

实现在Catalyst中。动态获取Filter内容做相关优化,当我们将一张大表和一张小表等值连接时,我们可以从小表侧收集一些统计信息,并在执行join前将其用于大表的扫描,进行分区修剪或数据过滤。可以大大提高性能

Runtime优化分两类:

  1. 全局优化:从提升全局资源利用率、消除数据倾斜、降低IO等角度做优化。包括AQE。

  2. 局部优化:提高某个task的执行效率,主要从提高CPU与内存利用率的角度进行优化。依赖Codegen技术。

Codegen

从提高cpu的利用率的角度来进行runtime优化。

  1. Expression级别

表达式常规递归求值语法树。需要做很多类型匹配、虚函数调用、对象创建等额外逻辑,这些overhead远超对表达式求值本身,为了消除这些overhead,Spark Codegen直接拼成求值表达式的java代码并进行即时编译

  1. WholeStage级别

传统的火山模型:SQL经过解析会生成一颗查询树,查询树的每个节点为Operator,火山模型把operator看成迭代器,每个迭代器提供一个next()接口。通过自顶向下的调用 next 接口,数据则自底向上的被拉取处理,火山模型的这种处理方式也称为拉取执行模型,每个Operator 只要关心自己的处理逻辑即可,耦合性低。

火山模型问题:数据以行为单位进行处理,不利于CPU cache 发挥作用;每处理一行需要调用多次next() 函数,而next()为虚函数调用。会有大量类型转换和虚函数调用。虚函数调用会导致CPU分支预测失败,从而导致严重的性能回退

Spark WholestageCodegen:为了消除这些overhead,会为物理计划生成类型确定的java代码。并进行即时编译和执行。

Codegen打破了Stage内部算子间的界限,拼出来跟原来的逻辑保持一致的裸的代码(通常是一个大循环)然后把拼成的代码编译成可执行文件。