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

79 阅读7分钟

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

今天是大数据专场基础班的第五次课,主要内容是介绍Spark 的原理与实践,主要分为下面四个板块。

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

1. 大数据处理技术栈

image.png

2. 常见大数据处理链路

image.png

3. Spark特点

  • 统一引擎,支持多种分布式场景
  • 多语言支持
  • 可读写丰富数据源
  • 丰富灵活的API/算子
  • 支持K8S/YARN/Mesos资源调度

4. Spark运行架构

image.png

二、 SparkCore原理解析

1. RDD

1.1 什么是 RDD ?

  • RDD(Resilient Distributed Dataset): 弹性分布式数据集,是一个容错的、并行的数据结构

1.2 如何创建RDD?

  • 内置RDD
    • ShuffleRDD/HadoopRDD/JDBCRDD
    • /KafkaRDD/ UnionRDD/MapPartitionsRDDI/...

image.png

  • 自定义RDD
    • class CustomRDD(...) extends RDD {}
    • 实现五要素对应的函数

1.3 RDD算子

  • 两类RDD算子
    • Transform算子:生成一个新的RDD
    • Action算子:触发Job 提交

1.4 RDD 依赖

  • RDD依赖:描述父子RDD之间的依赖关系(lineage)
    • 窄依赖: 父RDD的每个partition至多对应一个子RDD分区
      • NarrowDependency
      • OneToOneDependency
      • RangeDependency
      • PruneDependency
    • 宽依赖: 父RDD的每个partition都可能对应多个子RDD分区
      • ShuffleDependency

image.png

1.5 RDD 执行流程

  • Job: RDD action算子触发
  • Stage: 依据宽依赖划分
  • Task: Stage内执行单个partition任务

image.png

2. Scheduler

  • 根据 ShuffleDependency切分Stage,并按照依赖顺序调度Stage,为每个Stage生成并提交TaskSet 到TaskScheduler
  • 根据调度算法(FIFO/FAIR)对多个TaskSet进行调度,对于调度到的TaskSet,会将Task 调度(locality)到相关Executor上面执行,Executor SchedulerBackend提供

3. 内存管理

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

image.png

  • UnifiedMemoryManager 统一管理多个并发Task的内存分配
  • 每个Task获取的内存区间为1/(2*N)~1/N,N为当前Executor中正在并发运行的task数量

image.png

4. Shuffle

image.png

  • 每个MapTask生成一个Shuffle数据文件和一个index文件dataFile 中的数据按照partitionld进行排序,同一个partitionld的数据聚集在一起
  • indexFile保存了所有paritionld在dataFlle中的位置信息,方便后续ReduceTask 能 Fetch 到对应 partitionld的数据

image.png

  • External Shuffle Service + Dynamic Resource Allocation(DRA) image.png
  • shuffle write的文件被NodeManage r中的Shuffle Service 托管,供后续ReduceTask进行shuffle fetch,如果Executor空闲,DRA可以进行回收

三、 SparkSQL 原理解析

1. SparkSQL

image.png

image.png

2. Catalyst优化器 - RBO

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

image.png

  • Batch执行策略:

    • Once ->只执行一次

    • FixedPoint ->重复执行,直到plan不再改变,或者执行达到固定次数(默认100次)

3. Adaptive Query Execution(AQE)

  • 每个Task结束会发送MapStatus信息给Driver
  • Task的MapStatus中包含当前Task Shuffle产生的每个Partition的size统计信息
  • Driver获取到执行完的Stages的MapStatus信息之后,按照MapStatus中partition大小信息识别匹配一些优化场景,然后对后续未执行的Plan进行优化
  • 目前支持的优化场景:
    • Partiiton合并,优化shuffle读取,减少reduce task个数
    • SMJ -> BHJ
    • Skew Join优化

image.png

AQE框架三种优化场景:

  1. 动态合并shuffle分区(Dynamically coalescing shuffle partitions) Partition合并

image.png

image.png

  1. 动态调整Join策略(Dynamically switching join strategies) problem: Catalyst Optimizer优化阶段,算子的statistics估算不准确,生成的执行计划并不是最优

solution: AQE运行过程中动态获取准确Join的leftChild/rightChild的实际大小,将SortMergeJoin (SMJ) 转化为BroadcastHashJoin (BHJ)

image.png

  1. 动态优化数据倾斜Join(Dynamically optimizing skew joins) AQE根据MapStatus信息自动检测是否有倾斜,将大的partition拆分成多个Task进行Join

image.png

image.png

4. RuntimeFilter

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

image.png

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

5. Codegen

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

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

image.png

  • 将表达式中的大量虚函数调用压平到一个函数内部,类似手写代码
  1. WholeStage级别
  • 传统的火山模型:SQL经过解析会生成一颗查询树,查询树的每个节点为Operator,火山模型把operator看成迭代器,每个迭代器提供一个next()接口。通过自顶向下的调用 next 接口,数据则自底向上的被拉取处理,火山模型的这种处理方式也称为拉取执行模型,每个Operator 只要关心自己的处理逻辑即可,耦合性低。

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

image.png

  • 算子之间大量的虚函数调用,开销大
  • 将同一个Stage中的多个算子压平到一个函数内部进行执行 Spark WholestageCodegen:为了消除这些overhead,会为物理计划生成类型确定的java代码。并进行即时编译和执行。

image.png

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

四、 业界挑战与实践

1. Shuffle稳定性问题

  • 在大规模作业下,开源External Shuffle Service(ESS)的实现机制容易带来大量随机读导致的磁盘IOPS瓶颈、Fetch请求积压等问题,进而导致运算过程中经常会出现Stage重算甚至作业失败,继而引起资源使用的恶性循环,严重影响SLA

image.png

  • Shuffle稳定性解决方案

image.png

2. SQL 执行性能问题

压榨CPU资源

  • SQL执行性能解决方向
    • Photon: C++实现的向量化执行引擎
    • lntel: OAP/gazelle_plugin

3. 参数推荐/作业诊断

  • 问题
    • Spark参数很多,资源类/Shuffle/Join/Aggl ...调参难度大
    • 参数不合理的作业,对资源利用率/Shuffle稳定性/性能有非常大影响
    • 同时,线上作业失败/运行慢,用户排查难度大
  • 解决方向
    • 自动参数推荐/作业诊断

五、 课程总结

通过本节课的学习,让我对于大数据处理常见的场景链路有了大致的了解,以及Spark技术栈的相关知识,包括SparkCore中的RDD/调度/Shuffle/内存管理等概念机制,以及SQL在Spark引擎中执行的详细流程,了解目前业界主要遇到的挑战以及解决方案,学习思考问题是如何产生的、从而去寻找解决问题的思路。

引用参考

内容主要参考了刘畅老师在「Spark 原理与实践」课程里所教授的内容,同时也参考了学员手册里第二节的内容,图片来自于老师的PPT,链接如下:

  1. 【大数据专场 学习资料二】第四届字节跳动青训营 - 掘金 (juejin.cn)
  2. ​‌‍‬⁠⁡⁢⁣⁤Spark原理与实践 - 刘畅 - ppt.pptx - 飞书文档 (feishu.cn)