这是我参与「第四届青训营 」笔记创作活动的第5天
Spark 原理与实践
1.大数据处理引擎Spark介绍
大数据处理技术栈
常见大数据处理策略
Spark介绍
Spark 是一种与 Hadoop 相似的开源集群计算环境
用于大规模数据处理的统一分析引擎
Spark生态&特点
生态:
- 统一引擎,支持多种分布式场景
- 多语言支持
- 可读写丰富数据源
- 丰富灵活的 API /算子
- 支持K8S/ YARN / Mesos 资源调度
特点:
- 多语言支持:SQL、JAVA/Scala、R、Python
- 丰富数据源:内置DataSource、自定义DataSource
- 丰富的APL/算子:SparkCore->RDD、SparkSQL->DataFrame
Spark运行架构和部署方式
- Application(应用):Spark上运行的应用。Application中包含了一个驱动器(Driver)进程和集群上的多个执行器(Executor)进程。
- Driver Program(驱动器):整个应用管理者,负责作业调度
- Cluster Manager(集群管理器):控制Worker节点整个集群
- Worker Node(工作节点):集群上运行应用程序代码的任意一个节点。
- Executor(执行器):在集群工作节点上为某个应用启动的工作进程,该进程负责运行计算任务,并为应用程序存储数据。
#一个SparkComtext只有一个driver,负责作业的任务调度,运行程序的main函数
用户创建一个SparkComtext,连接到Cluster Manager,会根据用户 提交设置的参数、CPU、内存,来去分配资源。启动Executor。Driver Program会将用户程序划分为不同的Stage,每一个Stage会有完全相同Task,作用于待处理的分区。Driver Program会向Executor发送Task,会下载Task运行时的依赖,执行环境等,开始执行Task.并将实时的运行状态汇报给Driver Program,并做状态更新。
SparkCore原理解析
SparkCore
RDD
RDD:RDD弹性分布式数据集,是一个容错的、并行的数据结构
五个要素:
-
RDD有不同分区,每一个分区都会被一个计算任务处理
-
RDD 提供一系列最佳的计算位置
-
RDD之间有一系列的依赖关系
-
RDD实现了两种类型的分区函数(哈希、范围)
-
RDD每个分区都会有优先分区列表
RDD创建
-
内置RDD
-
自定义RDD
class CustomRDD(...)extends RDD {}实现五要素对应函数
RDD算子
RDD算子:对任何函数进行某一项操作都可以认为是一个算子,RDD算子是RDD的成员函数
两类:
- Transform算子:生成一个新的RD
map/filter/flatMap.....
- Action算子:触发Job提交
collect/count/take...
- Transform(转换)算子: 根据已有RDD创建新的RDD
- Action(动作)算子: 将在数据集上运行计算后的数值返回到驱动程序,从而触发真正的计算
RDD依赖
RDD依赖:描述父子RDD之间的依赖关系
-
窄依赖:父 RDD 的每个 partition 至多对应一个子 RDD 分区。
-
宽依赖:父 RDD 的每个 partition 都可能对应多个子 RDD 分区
RDD执行流程
Job : RDD action 算子触发 Stage :依据宽依赖划分 Task : Stage 内执行单个 partition 任务
划分Stage:从后往前推,遇到宽依赖就断开,划分为一个Stage。遇到窄依赖,就加入该Stage中。图最后一个阶段会为每个结果的Partition生成一个ResultTask。最后一个RDD的Partition数量决定每个Stage里面的Task数量。
调度器
当RDD对象创建后,SparkContext会根据RDD对象构建DAG有向无环图,然后将Task提交给DAGScheduler。
- 根据 ShuffleDependency 切分 Stage,并按照依赖顺序调度Stage,为每个Stage生成并提交 TaskSet到 TaskScheduler
- 根据调度算法(FIFO/FAIR)对多个TaskSet 进行调度,对于调度到的TaskSet,会将 Task 调度(locality)到相关 Executor上面执行,Executor SchedulerBackend提供
内存管理
- Executor 内存主要有两类:Storage、Execution
- UnifiedMemoryManager 统一管理 Storage/Execution 内存
- Storage 和 Execution 内存使用是动态调整,可以相互借用
- 当 Storage 空闲,Execution 可以借用 Storage 的内存使用
- 可以减少 spill 等操作,Execution 使用的内存不能被 Storage 驱逐
- 当 Execution 空闲,Storage 可以借用 Execution 的内存使用,
- 当 Execution 需要内存时,可以驱逐被 Storage借用的内存,直到 spark.memory.storageFraction 边界
多任务间内存分配
-
UnifiedMemoryManager 统一管理多个并发 Task 的内存分配
-
每个 Task 获取的内存区间为1/(2* N )~1/N,N 为当前 Executor 中正在并发运行的 task 数量
Shuffle
spark.shuffle.manager->sort
SortShuffleManager
每个 MapTask 生成一个 Shuffle 数据文件和一个 index 文件
External Shuffle Service
shufle write 的文件被 NodeManage r 中的 Shufle Service 托管,供后续 ReduceTask 进行 shufle fetch ,如果 Executor空闲,DRA 可以进行回收
SparkSQL原理解析
Catalyst优化器
- RBO:基于规则的优化
Batch 执行策略:
- Once ->只执行一次
- FixedPoint ->重复执行,直到 plan 不再改变,或者执行达到固定次数(默认100次)
- CBO:代价模型计算代价,选择最优,依赖于数据库对象的统计
AQE
AQE框架三种优化场景:
- 动态合并shuffle分区(Dynamically coalescing shuffle partitions)
Partiiton合并,优化shuffle读取,减少reduce task个数
- 动态调整Join策略(Dynamically switching join strategies)
SMJ -> BHJ
- 动态优化数据倾斜Join(Dynamically optimizing skew joins)
Skew Join优化
partition合并
作业运行过程中,根据前面运行完的Stage的MapStatus中实际的 partiiton大小信息,可以将多个相邻的较小的partiiton进行动态合并,由一个Task读取进行处理
SMJ -> BHJ
-
Catalyst Optimizer优化阶段,算子的statistics估算不准确,生成的执行计划并不是最优
-
AQE运行过程中动态获取准确Join的leftChild/rightChild的实际大小,将SMJ转换为BHJ
Skew Join优化
AQE根据MapStatus信息自动检测是否有倾斜
将大的partition拆分成多个Task进行Join
Runtime Filter
select x,y from A join B on A.a=B.b*
Runtime Filter减少了大表的扫描,shufile的数据量以及参加Join的数据量,所以对整个集群1O/网络/CPU有比较大的节省(10TB TPC-DS 获得了大约 35% 的改进)
Codegen
Expression
select (a+1)a from t
WholeStageCodegen
select (a+1)a from t where a=1
火山模型
业界挑战与实践
1. shuffle的稳定性问题:
在大规模作业下,开源 ExternalShuffleService(ESS)的实现机制容易带来大量随机读导致的磁盘 IOPS 瓶颈、Fetch请求积压等问题,进而导致运算过程中经常会出现 Stage 重算甚至作业失败,继而引起资源使用的恶性循环,严重影响 SLA.
2. SQL执行性能问题:
压榨CPU资源