这是我参与「第四届青训营 」笔记创作活动的第4天
1. 大数据处理引擎
2. SparkCore原理解析
3. SparkSQL原理解析
SparkCore:实现基本功能,任务调度、基本管理、错误恢复、存储、系统交互
SparkSQL:操作结构数据,交互查询数据源
MLlib:Spark提供的关于机器学习的算法数据库,分类回归聚类、模型评估、数据导入导出
GraphX:分布式图处理框架
Spark支持多种语言:SQL、java/scala、R、python
Spark支持多种数据源:Text、Parquet/ORC、JSON/CSV、JDBC
自定义DataSource,实现Datasource API对接HBase/Mongo..... spark-packages.org
Spark-丰富的API/算子
SparkCore→RDD弹性分布式数据集
SparkSQL→Dataframe
1 Spark运行架构&部署方式
Spark运行架构
提交-计算的过程:
1. 用户创建Sparkcontext,sparkcontext连接到cluster manager
2. Cluster manager分配计算资源,启动executor
3. Driver会把用户程序分到不同stage,而stage会由有一组完全相同的task组成
4. Driver向executor发送task
5. Executor下载task的运行依赖、准备执行环境
6. 应用实时将task的状态发给driver,driver状态更新并把task发到executor执行
7. 直到所有task执行完成
部署方式
1. Spark local mode 主要用于本地测试/单进程多线程模式
Spark-sql --master local[*]...
2. Spark standalone mode需要启动spark的standalone集群的master/worker,不需要外部的资源调度。
Spark-sql --master spark://{port}...
3. 依赖外部资源调度(YARN/K8S)
Spark-sql --master yarn ...
Spark-sql --master k8s://
2 SparkCore
2.1 Sparkcore总体
2.2 RDD
resilient distributed dataset:弹性分布式数据集,是Spark中最基本的数据抽象,代表一个不可变、可分区、元素可并行计算的集合。
RDD五要素
Partition:
RDD是一组Partition的列表(RDD内部的数据集在逻辑和物理上划分为多个partitions)。Spark中任务是以task线程的方式运行,一个Partition对应一个task线程。
Compute:
Spark中RDD的计算以Partition为单位,每个 RDD 都会实现compute()函数以达到这个目的。compute()函数会对迭代器进行复合,不需要保存每次计算的结果。
Dependencies:
RDD之间存在依赖关系。
由于RDD是只读的数据集,如果对RDD中的数据进行改动,就只能通过 Transformation 操作,由一个或多个RDD计算生成一个新的RDD,所以RDD之间就会形成类似 Pipeline的前后依赖关系,前面的称为parent RDD,后面的称为child RDD。当计算过程中出现异常情况导致部分 Partition 数据丢失时,Spark 可以通过这种依赖关系从父 RDD 中重新计算丢失的分区数据,而不需要对 RDD 中的所有分区全部重新计算,以提高迭代计算性能。
Partitioner:
两种类型的分区函数,一种是基于哈希的,另一种基于范围。只有对于Key-Value型的RDD才会有Partitioner,非Key-Value型的RDD的Partitioner值是None。
Preferred location:
“优先位置”,存储每个partition的一个优先位置的列表。
对于每个 HDFS 文件来说,这个列表保存的就是每个Partition所在block的位置。按照“移动数据不如移动计算”的理念,Spark 在进行任务调度的时候,会尽可能地优先将计算任务分配到其所要处理的数据块的存储位置,减少数据的网络传输,提升计算效率。
RDD算子(两类)
Transform算子:生成新的RDD
Action算子:触发job提交
RDD依赖:窄依赖和宽依赖
描述父子RDD之间的依赖关系。
窄依赖:父RDD的每个partition至多对应一个子RDD分区。
宽依赖:父RDD的每个partition都可能对应多个子RDD分区。
RDD执行流程
Stage:当spark执行作业时,会根据RDD之间的宽窄依赖关系,将DAG划分成多个相互依赖的stage。
Stage划分方式:从后往前推。如果遇到RDD之间为窄依赖,由于Partition依赖关系的确定性,Transformation操作可以在同一个线程里完成,窄依赖就被划分到同一个Stage中;如果遇到 RDD 之间为宽依赖(shuffledependency),则划分到一个新的Stage中,且新的Stage为之前Stage的 Parent,然后依次类推递归执行,Child Stage 需要等待所有的 Parent Stages 执行完成后才可以执行。
这样每个 Stage 内的 RDD 都尽可能在各个节点上并行地被执行,以提高运行效率。
最后一个RDD的partition的数量决定了最后task的数量。
2.3 Scheduler调度器
1.RDD创建后,spark会根据RDD对象形成DAG图。
2.触发job以后会将DAG提交给DAGscheduler,根据shuffledependency划分stage,并按照依赖顺序调度stage,为每个stage生成并提交taskset到taskscheduler。
3.根据调度算法(FIFO/FAIR)对多个Taskset进行调度。
4.得到调度的taskset,会将Task调度(locality)到相关Executor上面执行。
2.4 memory management
-Executor内存主要有两类:Storage/Execution
-unifiedmemorymanager统一管理storage/execution内存
-Storage和execution内存使用是动态调整,可以相互借用
-当storage空闲,Execution可以借用storage的内存使用
-execution使用的内存不能被storage驱逐
-当execution空闲,storage可以借用execution的内存使用
-当execution需要内存时,可以驱逐被storage借用的内存,直到spark.memory.storagefraction变节
Unifiedmemorymanager统一管理多个并发task的内存分配
每个task获取的内存区间为1/(2*N)~1/N,N为当前Executor中正在并发运行的task数量。
2.5 shuffle
Map和reduce之间数据重新分发的过程称为shuffle。
每个maptask生成一个shuffle数据文件和index索引文件。
详见shuffle章节的学习。
3 SparkSQL原理解析
SQL Query转化成RDD的过程:
3.1 Catalyst优化器
参照RBO/CBO内容
3.2 Adaptive Query Execution(AQE)自适应查询
根据运行时的统计数据,自动对正在运行的任务进行优化。边执行边优化,提高了SQL的执行效率。
Stage把它的task信息传给driver。(每个Task结束会发送MapStatus信息给DRIVER)
Coalescing shuffle partitions(partition合并)
Stage实际处理的数据不一样,可能某些stage的性能比较差。
如:
-partition参数对某个stage过大,则可能单个partition的大小比较小,且task个数会比较多,shuffle fetch阶段产生大量的小块随机读,影响性能
-partition参数对某个stage过小,则可能单个partition的大小比较大,会产生更多的spill或者OOM
作业运行中,根据前面运行完的stage的mapstatus中实际的partition大小信息,可以将多个相邻的较小的partition进行动态合并,由一个Task读取进行处理。
Catalyst Optimizer优化阶段,算子的statistics估算不准确,生成的执行计划并不是最优。
↓
AQE运行过程中动态获取准确join的leftChild/rightChild的实际大小,将SMJ(SortMergeJoin)转换为BHJ(BroadcastHashJoin)。
对于数据倾斜问题。AQE会根据shuffle的统计来自动检测是否有倾斜,江大的partition茶分成多个task进行join。
3.3 Runtime filter
对join算法的优化
Runtime Filter减少了达标的扫描,shuffle的数据量以及参加join的数据量,所以对整个集群IO/网络/CPU有比较大的节省。
附:Bloom Runtime Filter
3.4 Codegen - Expression
挑战和问题
Spark参数很多,资源类/Shuffle/Join/Agg/...调参难度大
参数不合理的作业,对资源利用率/Shuffle稳定性/性能有很大影响
线上作业失败/运行慢,用户排查难度大。