这是我参与「第四届青训营」笔记创作活动的第5天
0 引言
1 大数据处理引擎 Spark介绍
大数据基础知识总结:
- 数据:
- 存储:HDFS/Kafka/HBase,DeltaLake/Kudu/TOS/S3/...(没听过)
- 计算:Spark/Flink/YARN/K8S,Presto/ClickHouse/...(还没学过)
- 应用:BI报表/实时大盘/广告推荐/金融告警...(目前研究BI报表、告警系统、ML预测等)
个人对大数据处理链路理解:
- 数据源 -> 数据采集 -> 数据存储 -> 数据处理(ETL) -> 数据分析 -> 数据应用
Spark生态结构:
Spark核心组件:
- Spark Core、Spark SQL(SQL处理)、Spark Streaming(流处理)、MLlib(机器学习)、GraphX(图计算)
Spark特点:
- 统一引擎,支持多种分布式场景:
- 多语言支持:Java、Scala、Python、R、SQL
- 可读写丰富数据源:Text、Parquest/ORC(没听过)、JSON/CSV、JDBC、自定义
- 丰富灵活的API/算子:RDD、DataFrame(举例map/flatmap/...)
- 支持K8S/YARN/Mesos资源调度:
Spark运行架构:
- 在Application的main()函数中创建SparkContext;SparkContext负责和ClusterManager通信,进行资源的申请、任务的分配和监控等;ClusterManager将任务分配给多个Work Node;Executor作为一个进程运行在Worker节点上,该进程负责运行Task,并且负责将数据存在内存或者磁盘上。
Spark三种部署方式:
- Spark Local Model:本地测试/单进程多线程模式
- Spark Standalone Model:需要启动Spark的Standalone集群的Master/Worker
- on YARN/K8S:依赖外部资源调度器(YARN/K8S)
2 SparkCore原理解析
SparkCore是Spark的核心,主要负责任务调度等管理功能。SparkCore的实现依赖于 RDDs(Resilient Distributed Datasets,弹性分布式数据集)的程序抽象概念。
描述RDD的五要素:
- Partitions:分区列表
- Compute:用于计算每个分割的函数
- Dependencies:对其他RDD的依赖列表
- Partitioner:键值RDD的分区器(例如,表示RDD是哈希分区)
- PreferredLocations:计算每个拆分的首选位置列表(例如,anHDFS文件的块位置)
创建RDD:
- 内置RDD:ShuffleRDD/HadoopRDD/JDBCRDD
- 自定义RDD:class NewRDD(...) extends RDD{}。实现五要素对应的函数
两类RDD算子:
- Transform算子:生成一个新的RDD。map/filter/flatMap/groupByKey/reduceByKey/...
- Action算子:触发Job提交。collect/count/takelsaveAsTextFile/...
RDD依赖:
RDD依赖是描述父子RDD之间的依赖关系(lineage)。
- 窄依赖:父RDD的每个partition至多对应一个子RDD分区(NarrowDependency/PruneDependency/RangeDependency/OneToOneDependency)
- 宽依赖(会产生Shuffle):父RDD的每个partition都可能对应多个子RDD分区(ShuffleDependency)
RDD执行流程:
Job: RDD action算子触发、Stage:依据宽依赖划分、Task: Stage内执行单个partition任务
- 创建 RDD 对象
- DAGScheduler模块介入运算,计算RDD之间的依赖关系。RDD之间的依赖关系就形成了DAG(directed acyclic graph,有向无环图)
- 调度任务中将各阶段划分成不同的任务 (task),每个任务都是数据和计算的合体。每一个Job被分为多个Stage,划分Stage的一个主要依据是当前计算因子的输入是否是确定的,如果是则将其分在同一个Stage,避免多个Stage之间的消息传递开销。
Schedule调度器:
- DAGScheduler:根据Job构建基于Stage的DAG,并提交Stage给TASkScheduler。根据RDD和Stage之间的关系找出开销最小的调度方法,然后把Stage以TaskSet的形式提交给TaskScheduler。DAGScheduler决定了运行Task的理想位置,并把这些信息传递给下层的TaskScheduler。此外,DAGScheduler还处理由于Shuffle数据丢失导致的失败,这有可能需要重新提交运行之前的Stage(非Shuffle数据丢失导致的Task失败由TaskScheduler处理)。
- TASKSedulter: 将TaskSET提交给worker运行,每个Executor运行什么Task就是在此处分配的。TaskScheduler维护所有TaskSet,当Executor向Driver发生心跳时,TaskScheduler会根据资源剩余情况分配相应的Task。另外TaskScheduler还维护着所有Task的运行标签,重试失败的Task。
MemoryMangement内存管理:
Executor内存主要有两类:Storage、Execution
- UnifiedMemoryManager 统一管理Storage/Execution内存
- Storage和Execution内存使用是动态调整,可以相互借用当Storage空闲,Execution可以借用 Storage 的内存使用,
- 可以减少spill等操作,Execution使用的内存不能被Storage 驱逐
- 当Execution空闲,Storage可以借用Execution的内存使用,当Execution需要内存时,可以驱逐被Storage借用的内存,直到spark.memory.storageFraction边界
Shuffle:
类似于Hadoop中MapReduce中在Map和Reduce中间的处理,对相同的Key做聚合。
3 SparkSQL原理解析
Spark SQL工作流程:
- 在解析SQL语句之前,会创建SparkSession,涉及到表名、字段名称和字段类型的元数据(DataFrame/Dataset/SQL)都将保存在Catalog中;
- 当调用SparkSession的sql方法时就会使用SparkSqlParser进行解析SQL语句,解析过程中使用的ANTLR进行词法解析和语法解析;
- 接着使用Analyzer分析器绑定逻辑计划(Unresolved Logical Plan),在该阶段,Analyzer会使用Analyzer Rules,并结合Catalog,对未绑定的逻辑计划进行解析,生成已绑定的逻辑计划(resolved Logical Plan);
- 然后Optimizer根据预先定义好的规则(RBO或CBO)对 Resolved Logical Plan 进行优化并生成 Optimized Logical Plan(最优逻辑计划);
- 接着使用Spark Query Planner对优化后的逻辑计划进行转换,生成多个可以执行的物理计划Physical Plan;
- 接着CBO优化策略会根据Cost Model算出每个Physical Plan的代价,并选取代价最小的 Physical Plan作为最终的Selected Physical Plan;
- 最终使用QueryExecution执行物理计划,此时则调用SparkPlan的execute()方法,返回RDD。
SparkSQL核心:
-
Catalyst优化器
Catalyst核心:表示树和操作树的规则
1.Rule Based Optimizer(RBO):transformDown先序遍历树进行规则匹配、transformUp后序遍历树进行规则匹配
2.Cost Based Optimizer(CBO):JoinReorder、JoinSelection【Broadcast Hash Join(BHJ);Shuffle Hash Join(SHJ);Sort Merge Join(SMJ)】 -
AQE(Adaptive Query Execution)
- Coalescing Shuffle Partitions(PartitionReduce阶段使用合并)
作业运行过程中,根据前面运行完的Stage的MapStatus中实际的partiiton大小信息,可以将多个相邻的较小的partiiton进行动态合并,由一个Task读取进行处理。
spark.sql.adaptive.coalescePartitions.enabled spark.sql.adaptive.coalescePartitions.initialPartitionNum spark.sql.adaptive.advisoryPartitionSizelnBytes ...-
SMJ(SortMergeJoin)->BHJ(BroadcastHashJoin)
AQE运行过程中动态获取准确Join的leftChild/rightChild的实际大小,将SMJ转换为BHJ -
Skew Join(Optimizing Skew Join解决Join阶段数据倾斜)
AQE根据MapStatus信息自动检测是否有倾斜将大的partition拆分成多个Task进行Join
spark.sql.adaptive.skewJoin.enabled spark.sql.adaptive.skewJoin.skewedPartitionFactor spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes - Coalescing Shuffle Partitions(PartitionReduce阶段使用合并)
-
Runtime Filter
Runtime Filter减少了大表的扫描,shuffle的数据量以及参加Join的数据量,所以对整个集群IO/网络/CPU有比较大的节省(10TB TPC-DS获得了大约35%的改进)。 -
DataSource
-
Codegen
Expression(表达式):将表达式中的大量虚函数调用压平到一个函数内部,类似手写代码。
算子/WholeStageCodegen:算子之间大量的虚函数调用,开销大。将同一个Stage中的多个算子压平到一个函数内部进行执行。
4 业界挑战与实践
-
Shuffle稳定性问题
在大规模作业下,开源ExternalShufileService(ESS)的实现机制容易带来大量随机读导致的磁盘IOPS瓶颈、Fetch请求积压等问题,进而导致运算过程中经常会出现 Stage重算甚至作业失败,继而引起资源使用的恶性循环,严重影响SLA.
解决:uber和alibaba/RemoteShuffleService、tencent/Firestorm、byteDance/下节课程 -
SQL执行性能问题
对于Native Engine解决办法:Vectorized、Codegen、Vectorized+Codegen -
参数推荐/作业诊断
问题:参数多、作业失败排查困难
解决:设计自动化组件处理,ML/DL?
参考信息
1.【大数据专场 学习资料二】第四届字节跳动青训营
2. Spark资源调度和任务调度过程介绍
3. Spark运行架构
4. Spark SQL架构的工作原理和工作流程
5. 深入理解Spark SQL的Catalyst Optimizer