Spark原理与实践|青训营笔记
一、课程概述
- 大数据处理常见的场景链路
- Spark技术栈,SparkCore中RDD/调度/Shuffle/内存管理等概念和机制,以及SQL在Spark引擎中执行的详细流程
- 业界主要挑战及解决方案
二、详细内容
1. 大数据处理引擎Spark介绍
常见的大数据处理pipeline、Spark技术栈。
-
应用:BI报表/实时大盘/广告/推荐
-
计算:Spark/Flink/Presto/Impala/ClickHouse...YARN/K8s
-
存储:MetaStore/Paequet/ORC/DeltaLake/Hudi/Iceberg/HDFS/Kafka/HBase/Kudu/TOS/S3...
-
数据:Volume/Variety/Velocity;On-Premise/On-cloud;平台,管理,安全...
-
开源大数据处理引擎
- Batch:Hive/hadoop/Spark
- streaming:Flink
- OLAP:Presto/ClickHouse/Impala/DORIS
-
多源数据处理/SQL分析/大数据分析/单机机器学习到大数据分析
-
生态和特点
- 统一引擎支持多种分布式场景
- 多语言支持 SQL/Python/Java/Scala/R
- 可读写丰富的数据源 内置DataSource及自定义DataSource
- 丰富灵活的API/算子:SparkCore->RDD/ SparkSQL->DataFrame
- 支持K8S/YARN/Mesos资源调度
1.1 Spark运行架构
- 用户提交程序到Cluster Manager,Cluster Manager分配资源Worker Node, Worker Node访问Driver Program获取Context,然后开始Task;
- 模式
- Local Mode:本地测试/单进程多线程模式
- Standalone Mode:需要启动Spark的Standalone集群的Master和Worker (自带完整分布式集群)
- ON YARN/K8S:依赖外部资源调度器
//切换模式
spark-sql --master
1.2 Spark工作原理
- 下载Spark包
- 设置home 设置path环境变量
- 交互界面:spark-shell spark-sql pyspark SparkPi编译成jar包后提交
- SparkUI 查看运行信息:JOB STAGE 环境信息
- 运行中:http://driver_host:port
- 运行后:SparkHistoryServer查看log
2. SparkCore原理解析
RDD->资源管理/DAG、Task调度/内存管理/shuffle
2.1 RDD
Resilient Distributed Dataset: represents an immutable, partitioned collection of elements that can be operated on in parallel;
2.1.1 RDD 五要素
- Partitions:list of partitions
- compute:计算函数 function for computing each split
- dependencies: a list of dependencies on other RDDs
- partitioner: a partitioner for key-value RDDs
- preferred locations:存储有限位置,移动计算到需要处理的数据位置 list of preferred locations to compute each split on (block locations for an HDFS file)
2.1.2 RDD的执行流程
-
计算结果进行缓存/数据集缓存 def cache
-
创建RDD
- 内置RDD:shuffleRDD/HadoopRDD...
- 自定义RDD
-
RDD 算子
- Transform算子:生成一个新的RDD
- Action算子:触发Job提交
-
RDD 依赖:描述父子RDD之间的依赖关系(lineage)
- 窄依赖:父RDD的每个partition至多对应一个子RDD的分区
- NarrowDependency
- OneToOneDependency
- RangeDependency:如果子RDD partition在父RDD range内
- Prune Dependency
- 宽依赖:可以对应多个子RDD分区
- shuffleDependency
- shuffleDependency
- 窄依赖:父RDD的每个partition至多对应一个子RDD的分区
-
RDD数据分区丢失时,Spark会对数据进行重算;有可能所有父RDD需要重算,可以通过checkpoint写入数据
-
执行流程
- 宽依赖划分Stage
- Job: RDD action算子触发
- Task:Stage内执行单个partition任务
-
调度器
- 根据shuffleDependency切分Stage并按照依赖顺序调度Stage,为每个Stage生成并提交TaskSet到TaskScheduler
- 根据调度算法对多Taskset进行调度,调度到相关Executor上执行 (按照Locality)
2.1.3 内存管理
-
两类:Storage (在partition和broadcast时划分的内存)、Execution(在shuffle/join/sort/agg时计算需要的内存)
-
UnifyMemoryManager统一管理两类内存
-
存储和执行内存是动态的 可以借用对方的空闲空间
-
多任务间内存分配
- UnifiedMemoryManager统一管理多个并发Task的内存分配
- 内存不足时不能进行,需要等待资源足够继续
-
Shuffle
- 创建shuffle manager
- SortShuffleManager:join两个比较大的表,对数据进行排序,进行spill产生多个spillFile临时文件,将其合并至一个shuffle dataFile,并创建一个indexFile
- external shuffle service:运行在主机上管理所有产生的shuffle数据,供后续reduceTask进行shuffle fetch
- 创建shuffle manager
3. SparkSQL原理解析
- 优化执行计划
- 运行时优化(尽快完成计划):提高全局资源利用率/局部优化
3.1 SparkSQL执行过程
graph LR
1[SQLQuery/DataFrame] --> 2[Unresolved Logical Plan] --catalog--> 3[Logical Plan]--logicalOptimization-->4[optimized logical plan]
graph LR
4[optimized logical plan]--PhysicalPlanning-->5[physical plans]--costModel-->6[selected physical plan]--codeGeneration-->7[executable]
3.2 Catalyst优化
- RBO
- 使用RuleExecutor,每个batch代表一个rule集合
- 两种执行策略
- once:只执行一次
- fixedpoint:重复执行直到plan不再改变或达到固定次数
- 两种遍历规则
- transformDown 先序遍历树进行规则匹配
- transformUp 后序遍历树进行规则匹配
- CBO
- 根据优化规则生成多个执行计划,计算代价选择最优方案,依赖数据库对象的统计(ANALYZE TABLE收集表信息)进行估算
- JoinSelection
- Broadcast Join:大表和小表
- Shuffle Hash Join
- SortMergeJoin:大表
3.3 AQE Adaptive Query Execution
- 根据已经完成计划的反馈进行统计,优化后续的task
- 每个task结束后发送mapstatus信息给driver
- 支持的优化场景:
- partition合并 优化shuffle读取 减少
- SMJ->BHJ
- skew join优化
3.3.1 coalescing shuffle partitions
- partition合并方法:作业运行过程中根据前面运行完的stage的mapStatus中实际的partition大小信息,可以将多个相邻的较小的partition进行动态合并,由一个task读取进行处理
- 开始设定较多的partition分区,在shuffle中打乱到更多的分区,然后聚合其中较小的分区;由此可以减少task的数量
3.3.2 switching join strategies
SMJ->BHJ
- catalyst optimizer优化阶段算子的statistics估算不准确,生成的执行计划并不是最优 (例如大表但有filter变小表)
- AQE运行过程中动态获取准确Join的实际表大小,将SMJ转换为BHJ
3.3.3 optimizing skew joins
- AQE根据mapStatus信息自动检测倾斜,将大的partition拆分成多个task进行join
- 会增加task,但由于原本一个task时间非常长,在优化后可以均匀时间
3.4 RuntimeFilter
- 将filter尽量下推到靠近数据源的位置
- 减少了大表的扫描,shuffle的数据量以及参加join的数据量,对整个集群IO/网络/CPU有比较大的节省
- Bloom RuntimeFilter:利用bloomfilter对数据源进行过滤
3.5 Codegen
- 表达式 Expression:将表达式中的大量虚函数调用压平到一个函数内部,类似手写代码 (类型匹配、虚函数、调用对象)-> 消除overhead 直接写成表达式
- 算子/wholeStageCodegen:算子之间大量的虚函数调用,开销很大;将同一个stage中的多个算子压平到一个函数内部进行执行
4.业界挑战与实践
4.1 shuffle稳定性问题
在大规模作业下 开源ESS的实现机制容易带来大量随机读导致磁盘的IOPS瓶颈、fetch请求积压等问题,进而导致运算过程中经常出现stage重算及作业失败继而引起资源使循环
- 解决方案:RemoteShuffleService/ CloudShuffleService(见下节课)
4.2 SQL执行性能问题
压榨CPU资源 CPU瓶颈
- 超标量流水线/乱序执行/分支预测 并行程序越多越好/CPU缓存友好(后续cache预存)/SIMD(单指令多数据流)
- Vectorized 向量化(拉取模式函数返回一批 CPU开销一组数据分摊 适用于列存储 缺点中间数据很大)/ Codegen (打破算子之间界限,复合算子)
- Codegen限制Java代码,相对native C++等性能有缺陷,无法进行SIMD优化
4.3 参数推荐/作业诊断
-
问题:
- Spark参数很多,资源类/shuffle/join/agg等,调参难度大
- 参数不合理的作业对资源利用率/shuffle稳定性/性能有非常大影响
- 线上作业失败/运行慢,用户排查难度大
-
解决方案:
- 自动参数推荐/作业诊断
三、实践例
见上文4。
四、个人总结
- 难点内容
- Spark的应用、RDD的核心调度、内存管理机制
- shuffle机制
- Spark的优化
- 个人总结 本节课学习了Spark大数据处理引擎的内容,接触了RDD、调度机制、内存管理机制和shuffle机制。内容比较抽象,需要结合实例代码进一步学习。