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

191 阅读6分钟

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

1. spark介绍

大数据处理技术栈

  • 应用:BI报表/实时大盘/广告/推荐……
  • 计算:Spark/Fink
  • 存储:MetaStore、HDFS,HBase
  • 数据:Vloume

常用大数据处理链路

image.png

开源大数据处理引擎

  • 批处理:hadoop,hive,spark
  • 流处理:flink
  • OLAP:presto,Impala,ClickHouse

spark

生态:完备

特点:多语言支持,丰富的数据源,丰富的api/算子

运行架构&部署方式:本地测试/单进程多线程模式;集群模式;在云环境

2.SparkCore原理解析:

Spark Core:Spark核心组件,它实现了Spark的基本功能,包含任务调度、内存管理、错误恢复、与存储系统交互等模块。

定义:RDD(Resilient Distributed Datasets),弹性分布式数据集

创建:内置/自定义

分类:

  • Transform:生成一个新的RDD
  • Action:触发job提交

依赖:描述父子RDD之间的依赖关系

  • 窄依赖:父RDD的每个partition至多对应一个子RDD分区
  • 宽依赖:父RDD的每个partition都可能对应多个子RDD分区

执行流程

  1. job:RDD action算子触发 -> runJob()
  2. Stage:依据ShuffleDependency宽依赖划分Stage,并按照依赖顺序调度Stage,为每个Stage生成并提交TAskSet到TaskScheduler
  3. Task:Stage内执行单个partition任务。根据调度算法对多个TAskSet进行调度,对于调度到的TaskSet,会将Task调度(locality)到相关Executor上面执行,Executor SchedulerBackend提供

内存管理: 分为存储(Storage)和执行(Exection)两类

  • 特点:统一管理两类内存,动态调整可以相互借用

    • 当Storage空闲,Execution可以借用Storage的内存使用,减少spill等操作,Execution使用的内存不能被Storage驱逐
    • 当Execution空闲,Storage可以借用Execution的内存使用。当Execution需要内存时,可以驱逐被Storage借用的内存,直到达到边界
  • 多任务间内存分配:Memory Management统一管理多个并法Task的内存分配

shuffle:

  • 处理读写请求:External Shuffle Service

    • 每个MapTask生成一个Shuffle数据文件和一个index文件
  • shuffle write的文件被NM中的Shuffle Service托管,供后续Reduce Task进行shuffle fetch,如果Executor空闲,DRA可以进行回收

3.SparkSQL原理解析:

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

img

3.1SparkSQL执行过程

  • 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:执行计划的优化,目标是找出最优的执行计划
  2. Runtime:运行时优化,目标是在既定的执行计划下尽可能快的执行完毕。

3.2Catalyst优化

  1. Rule Based Optimizer(RBO): 基于规则优化,对语法树进行一次遍历,模式匹配能够满足特定规则的节点,再进行相应的等价转换。
  2. Cost Based Optimizer(CBO): 基于代价优化,根据优化规则对关系表达式进行转换,生成多个执行计划,然后CBO会通过根据统计信息(Statistics)和代价模型(Cost Model)计算各种可能执行计划的代价,从中选用COST最低的执行方案,作为实际运行方案。CBO依赖数据库对象的统计信息,统计信息的准确与否会影响CBO做出最优的选择。

3.3AQE

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

AQE框架三种优化场景:

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

3.4 RuntimeFilter

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

Runtime优化分两类:

  1. 全局优化:从提升全局资源利用率、消除数据倾斜、降低IO等角度做优化。包括AQE。
  2. 局部优化:提高某个task的执行效率,主要从提高CPU与内存利用率的角度进行优化。依赖Codegen技术。

引入Bloom Runtime Filter

3.5Codegen

从提高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内部算子间的界限,拼出来跟原来的逻辑保持一致的裸的代码(通常是一个大循环)然后把拼成的代码编译成可执行文件。

4.业界挑战与实践

目标:了解三种业内Spark面临的问题及解决策略,学习思考问题的产生、寻找解决问题思路。

  1. shuffle的稳定性问题——数据远端存储
  2. SQL执行性能问题——向量法/Codegen/两者结合,例如Intel:OAP
  3. 参数推荐/作业诊断——自动化