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

181 阅读7分钟

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

一、课程概述

  1. 大数据处理常见的场景链路
  2. Spark技术栈,SparkCore中RDD/调度/Shuffle/内存管理等概念和机制,以及SQL在Spark引擎中执行的详细流程
  3. 业界主要挑战及解决方案

二、详细内容

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运行架构

Screen Shot 2022-07-26 at 4.15.20 AM.png

  • 用户提交程序到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 Screen Shot 2022-07-26 at 4.32.50 AM.png
  • 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

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机制。内容比较抽象,需要结合实例代码进一步学习。

五、引用信息

【大数据专场 学习资料二】第四届字节跳动青训营