Spark 原理与实践

339 阅读10分钟

青训营Spark 原理与实践

2022の夏天,半壶水响叮当的我决定充实一下自我


一、内容介绍

    青训营

大数据处理引擎Spark介绍

Spark生态组件:

  • Spark Core:Spark核心组件,它实现了Spark的基本功能,包含任务调度、内存管理、错误恢复、与存储系统交互等模块。
  • Spark SQL:用来操作结构化数据的核心组件,通过Spark SQL可以直接查询Hive、HBase等多种外部数据源中的数据。
  • Spark Structured Streaming:Spark提供的流式计算框架,支持高吞吐量、可容错处理的实时流式数据处理。
  • MLlib:Spark提供的关于机器学习功能的算法程序库,包括分类、回归、聚类、协同过滤算法等,还提供了模型评估、数据导入等额外的功能。
  • GraphX:Spark提供的分布式图处理框架,拥有对图计算和图挖掘算法的API接口以及丰富的功能和运算符。
  • 独立调度器、Yarn、Mesos、Kubernetes:Spark框架可以高效地在一个到数千个节点之间伸缩计算,集群管理器则主要负责各个节点的资源管理工作,为了实现这样的要求,同时获得最大灵活性,Spark支持在各种集群管理器(Cluster Manager)上运行。

Spark 运行架构和工作原理:

  • Application(应用):Spark上运行的应用。Application中包含了一个驱动器(Driver)进程和集群上的多个执行器(Executor)进程。
  • Driver Program(驱动器):运行main()方法并创建SparkContext的进程。
  • Cluster Manager(集群管理器):用于在集群上申请资源的外部服务(如:独立部署的集群管理器、Mesos或者Yarn)
  • Worker Node(工作节点):集群上运行应用程序代码的任意一个节点。
  • Executor(执行器):在集群工作节点上为某个应用启动的工作进程,该进程负责运行计算任务,并为应用程序存储数据。
  • Task(任务):执行器的工作单元。
  • Job(作业):一个并行计算作业,由一组任务(Task)组成,并由Spark的行动(Action)算子(如:save、collect)触发启动。
  • Stage(阶段):每个Job可以划分为更小的Task集合,每组任务被称为Stage。

Spark目前支持几个集群管理器:

  • Standalone :Spark 附带的简单集群管理器,可以轻松设置集群。
  • Apache Mesos:通用集群管理器,也可以运行 Hadoop MapReduce 和服务应用程序。(已弃用)
  • Hadoop YARN: Hadoop 2 和 3 中的资源管理器。
  • Kubernetes:用于自动部署、扩展和管理容器化应用程序的开源系统。

SparkCore

  • RDD(Resilient Distributed Dataset):弹性分布式数据集,是一个容错的、并行的数据结构
  • RDD算子:对任何函数进行某一项操作都可以认为是一个算子,RDD算子是RDD的成员函数
  • Transform(转换)算子: 根据已有RDD创建新的RDD
  • Action(动作)算子: 将在数据集上运行计算后的数值返回到驱动程序,从而触发真正的计算
  • DAG(Directed Acyclic Graph): 有向无环图,Spark中的RDD通过一系列的转换算子操作和行动算子操作形成了一个DAG
  • DAGScheduler:将作业的DAG划分成不同的Stage,每个Stage都是TaskSet任务集合,并以TaskSet为单位提交给TaskScheduler。
  • TaskScheduler:通过TaskSetManager管理Task,并通过集群中的资源管理器(Standalone模式下是Master,Yarn模式下是ResourceManager)把Task发给集群中Worker的Executor
  • Shuffle:Spark中数据重分发的一种机制。

SparkSQL

  • DataFrame: 是一种以RDD为基础的分布式数据集, 被称为SchemaRDD
  • Catalyst:SparkSQL核心模块,主要是对执行过程中的执行计划进行处理和优化
  • DataSource:Spark/SQL支持通过 DataFrame 接口对各种数据源进行操作。
  • Adaptive Query Execution:自适应查询执行
  • Runtime Filter:运行时过滤
  • Codegen:生成程序代码的技术或系统,可以在运行时环境中独立于生成器系统使用

SparkSql执行过程:

  • Unresolved Logical Plan:未解析的逻辑计划,仅仅是数据结构,不包含任何数据信息。
  • Logical Plan:解析后的逻辑计划,节点中绑定了各种优化信息。
  • Optimized Logical Plan:优化后的逻辑计划
  • Physical Plans:物理计划列表
  • Selected Physical Plan 从列表中按照一定的策略选取最优的物理计划

业界挑战与实践

  • 向量化(vectorization):将循环转换为向量操作的编译器优化
  • 代码生成(Codegen:Code generation):生成程序代码的技术或系统,可以在运行时环境中独立于生成器系统使用

二、大数据处理引擎 Spark介绍

    Spark:大数据批处理计算环节

大数据处理技术栈 image.png

常见大数据处理链路 image.png

开源大数据处理引擎 image.png Batch批式     Streaming流式      OLAP联机分析处理

2.1Spark概念

官网:用于大规模数据处理的统一分析(计算)引擎
多语言引擎,可用于单机结点或集群
可执行数据工程,数据科学,机械学习
特点:
1.Batch/streaming data
用统一方式处理流批数据,多语言选择:Python,SQL,Scala,Java, R
2.SQL分析
用仪表板和临时报告等快速执行分布式ANSI(快速) SQL查询。运行速度比大多数数据仓库都快
3.大规模数据科学
对千兆(PB)比例尺数据执行探索性数据分析(EDA,电子设计自动化)
4.机器学习
单机上的训练机学习算法,并方便扩大到大规模计算机集群上。

2.2Spark生态&特点

见上 Spark生态组件 image.png

  • 统一引擎,支持多种分布式场景
  • 多语言支持
  • 可读写丰富数据源
  • 丰富灵活的API/算子
  • 支持K8S/YARN/Mesos资源调度

2.3Spark多语言支持

SELECT
    name.first AS first_name,
    name.last AS last_name,
    age
FROM json.`logs.json`
    WHERE age > 21;
    
Dataset df = spark.read().json ( "logs.json") ;
df.where("age > 21")
    .select("name.first" ).show( ) ;

val df = spark.read.json ("logs.json")
df.where("age > 21")
    .select ( "name.first" ).show()
    
df <- read.json(path = "logs.json")
df <- filter(df,df$age > 21)
head(select(df, df$name.first))

df = spark.read.json("logs.json")
df.where("age > 21").select("name.first").show()

2.4Spark丰富数据源

  • 内置 DataSource: Text
    Parquet/ORC
    JSON/CSV
    JDBC

  • 自定义DataSource 实现 DataSource V1/V2 API
    HBase/Mongo/ElasticSearch/...
    一个用于Apache SPark的第三方包的社区索引
    spark-packages.org

2.5Spark丰富API/算子(函数操作)

  • SparkCore ->RDD(弹性分布式数据集,是一个容错的、并行的数据结构)

map/filter/flatMap/mapPartitions
地图/过滤器/平面地图/地图部分

repartition/groupBy/reduceByljoin/aggregate
重新分区/组/还原-Byljoin/聚合

foreach/foreachPartition
预测/预测划分

count/max/min
计数/最大/分钟

  • SparkSQL-> DataFrame(RDD为基础分布式数据集,传统数据库二维表格,含数据,数据结构信息,Hive)

select/filterlgroupBylagg/join/union/orderByl...

Hive(单元) UDF/自定义UDF

2.6Spark运行架构&部署方式

支持多资源调度:

运行/执行架构:master&slave架构
Cluster Manager集群管理器:控制集群,监控worker结点,负责资源管理与调度
worker结点,worker manager:控制计算节点,开启Executor进程,负责Executor与Task执行
Tast任务:Driver Program,一个app mast,整个应用管理者
Spark Application只有一个Driver Program
Driver Program:负责作业任务调度,一个JVM进程,运行程序内函数,创建一个Spark Context上下文(控制整个应用生命周期)
Executor:实际执行任务Tast
image (38).png Spark Context链接Cluster Manager
Cluster Manager依据参数CPU,内存分配资源,启动Executor进程
Driver Program将程序Application划分Stage(阶段)
Stage(阶段)由相同task任务构成,task分别作用于不同分区待处理数据
Driver 向Executor发送task,Executor下载task下载运行依赖,环境,并执行task,并将task运行状态反馈Driver,Driver进行状态更新,调用task给Executor执行,直到次数或时间限制

部署方式:

  • Spark Local Mode
    本地测试/单进程多线程模式
spark-sql --master local[*] ...
  • Spark Standalone Mode
    需要启动Spark的Standalone集群的Master/Worker
spark-sql --master spark://${master_ip]:${port}...
  • On YARN/K8S
    依赖外部资源调度器(YARN/K8S)
spark-sql --master yarn ...
spark-sql --master k8s://https:/l<k8s-apiserver-host>:<k8s-apiserver-port>

2.7Spark下载

image.png

2.8Spark提交命令

环境变量 image (39).png

spark-shell image (40).png

spark-sql image (41).png

pyspark image (42).png

2.8Spark提交一个简单任务

package org.apache.spark.examples

import scala.math.random
import org.apache.spark.sql.sparkSession

/** Computes an approximation to pi
    计算π的近似值
 */
object SparkPi {
    def main(args: Array [string]): Unit = {
        val spark = SparkSession
            .builder
            .appName("spark Pi")
            .get0rCreate()
        val slices = if (args.length > 0) args(0).toInt else 2
        val n = math.min(100000L * slices,Int.MaxValue).toInt 
        // avoid overflow
        val count = spark.sparkContext.parallelize( 1 until n,slices).map { i =>
            val x = random * 2 - 1
            val y = random * 2 - 1
            if (x*x + y*y <= 1) 1 else 0
        }.reduce(_ + _)
        println(s"Pi is roughly ${4.0 * count / (n - 1)}")
        spark.stop()
    }
}//蒙特卡罗随机数求pi

编译成jar包之后,使用spark-submit提交 image (43).png

image (44).png

2.8Spark UI查看任务更详细信息

image (45).png

image.png

2.9Spark性能benchmark

image.png


三、SparkCore原理解析

  • RDD(Resilient Distributed Dataset):弹性分布式数据集,是一个容错的、并行的数据结构
  • DAG/task调度
  • Shuffle:Spark中数据重分发的一种机制。
  • 内存管理机制

image.png

3.1基本单元:RDD

  • RDD(Resilient Distributed Dataset):弹性分布式数据集,是一个容错的、并行的数据结构

描述RDD的五要素

要素解释
Partitions/分区分区个数(集合默认CPU数)对应task处理,并行计算数量
Compute/计算计算函数个数与分区数对应
Dependencies/相依性RDD依赖其他RDD(前后依赖关系,不用从头开始)
Partitioner/分割器键值RDD的分区器(例如,抛出RDD是hash的)
PreferredLocations/预测位置可计算每个分区的优先位置(例如,HDFS 文件的块位置)
abstract class RDD{
def getPartitions: Array[Partition]...
def compute(split: Partition...
def getDependencies: Seq[Dependency[_]]...
val partitioner.Option[Partitioner]...
def getPreferredLocations(split:Partition)...

//算子
def map(f:T=> U): RDD[U]
def filter(f:T=> Boolean): RDD[T]
...
def count(): Long
def cache()//缓存
def persist()//缓存
}

3.1创建RDD

image.png

3.2两类RDD

  • Transform算子:生成一个RDD
  • Action算子:(commit)触发Job提交 image.png

3.3RDD依赖

image.png image (46).png RDD依赖下可依据子分区数据恢复运算

3.4RDD执行

  • Job: RDD action算子触发
  • Stage:依据宽依赖划分
  • Task: Stage内执行单个partition任务
  • image (3).jpeg 划分Stage的整体思路:从后往前推,遇到宽依赖就断开,划分为一个Stage。遇到窄依赖,就将这个RDD加入该Stage中,DAG最后一个阶段会为每个结果的Partition生成一个ResultTask。每个Stage里面的Task数量由最后一个RDD的Partition数量决定,其余的阶段会生成ShuffleMapTask。

当RDD对象创建后,SparkContext会根据RDD对象构建DAG有向无环图,然后将Task提交给DAGScheduler。DAGScheduler根据ShuffleDependency将DAG划分为不同的Stage,为每个Stage生成TaskSet任务集合,并以TaskSet为单位提交给TaskScheduler。TaskScheduler根据调度算法(FIFO/FAIR)对多个TaskSet进行调度,并通过集群中的资源管理器(Standalone模式下是Master,Yarn模式下是ResourceManager)把Task调度(locality)到集群中Worker的Executor,Executor由SchedulerBackend提供。

3.5 Scheduler/调度程序

image.png

3.6 Memory Management/内存管理机制

image.png image.png Spark 作为一个基于内存的分布式计算引擎,Spark采用统一内存管理机制。重点在于动态占用机制。

  • 设定基本的存储内存(Storage)和执行内存(Execution)区域,该设定确定了双方各自拥有的空间的范围,UnifiedMemoryManager统一管理Storage/Execution内存

  • 双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间

  • 当Storage空闲,Execution可以借用Storage的内存使用,可以减少spill等操作, Execution内存不能被Storage驱逐。Execution内存的空间被Storage内存占用后,可让对方将占用的部分转存到硬盘,然后"归还"借用的空间

  • 当Execution空闲,Storage可以借用Execution内存使用,当Execution需要内存时,可以驱逐被Storage借用的内存,可让对方将占用的部分转存到硬盘,然后"归还"借用的空间

3.6.1 多任务间内存分配

UnifiedMemoryManager 统一管理多个并发Task的内存分配,空间不够则阻塞,直到足够空间唤醒

每个Task获取的内存区间为1/(2*N)~1/N,N为当前Executor中正在并发运行的task数量

image.png

3.7 Shuffle

image.png Map与Reduce间数据处理,重分发过程

spark.shuffle.manager -> sort

trait ShuffleManager {
    def registerShuffle...def getWriter...
    def getReader...
    def unregisterShuffle...
    }
class SortShuffleManager extends ShuffleManager

image.png

image.png


四、SparkSQL原理解析

4.1.基本流程

image.png SQL Query查询语句转抽象ST语法树->Unresolved Logical Plan未解析的逻辑计划->Analysis解析计划,绑定数据,依据数据Catalog信息对数据解析->解析后逻辑计划->逻辑计划优化Catalyst(RBO/CBO)-> Physical Planning将逻辑转物理实行计划->依据Cost Model过去信息统计(CBO)->选择最佳物理执行计划->代码转换为JAVA,实现RDDs

4.2.Catalyst优化器

image (47).png

  • RBO

image.png image.png

  • CBO

image.png

4.3.自查询Adaptive Query Execution(AQE)

边执行,边优化,依完成结点,真实统计不停反馈 image.png

4.3.1 AQE- Coalescing Shuffle Partitions

image.png

4.3.2 AQE- Switching Join Strategies

image.png

4.3.3 AQE- Optimizing Skew Joins

image.png

4.4 Runtime Filter

image.png

4.5 Bloom Runtime Filter

链接 [spark/q16.sql at a78d6ce376edf2a8836e01f47b9dff5371058d4c · apache/spark · GitHub]

4.6 Codegen - Expression

image.png

4.6.1 Codegen - WholeStageCodegen

image.png 动态生成代码,Janino即时编译执行 image (48).png image.png image (49).png


五、业界挑战与实践

5.1 Shuffle稳定性

问题:在大规模作业下,开源ExternalShuffleService(ESS)的实现机制容易带来大量随机读导致的磁盘IOPS瓶颈、Fetch请求积压等问题,进而导致运算过程中经常会出现 Stage重算甚至作业失败,继而引起资源使用的恶性循环,严重影响SLA image.png

解决: image.png

5.2 SQL执行性能

问题: image.png 解决: image.png

5.3 参数推荐/作业诊断

image.png


晚安玛卡巴卡

快乐暑假