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

120 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的第5天

5.1 大数据处理引擎Spark介绍

1.大数据处理技术栈

image.png

2.常见大数据处理链路

image.png

3.开源大数据处理引擎

image.png

4.什么是Spark?

官网:

image.png

image.png

5.Spark版本演进

image.png

6.Spark生态与特点

image.png

1)统一引擎, 支持多种分布式场景

2)多语言支持

3)可读写丰富数据源

4)丰富灵活的API算子

5)支持K8S/YARN/Mesos资源调度

7.Spark特点-多语言支持

SQL:

image.png

Java/Scala:

image.png

R:

image.png

Python

image.png

8.Spark特点-丰富数据源

1)内置datasource:Text/Parquet/ORC/JSON/CSV/JDBC

2)自定义datasource:实现datasourceV1/V2 API HBase/Mongo/ElasticSearch

A community index of third-party packages for Apache Spark:

spark-packages.org

9.Spark特点-丰富的API/算子

SparkCore->RDD

map/filter/flatMap/mapPartitions

repartition/groupBy/reduceBy/join/aggregate

foreach/foreachPartition

count/max/min

SparkSQL-> DataFrame

select/filter/groupBylaggljoin/union/orderByl...

Hive UDF/自定义UDF

10.Spark运行框架&部署方式

image.png

Spark Local Mode

本地测试/单进程多进程模式

spark-sql--master local[*]...

Spark Standalone Mode

需要启动Spark的Standalone集群的Master/Worker spark-sql--master spark://masterip:{master_ip}:{port}...

On YARN/K8S

依赖外部资源调度器(YARN/K8S)

spark-sql--master yarn ...

spark-sql--master k8s://https://:...

11.spark下载编译

spark包

编译:

image.png

下载:官网download

image.png

12.spark包概览:

image.png

image.png

13.spark提交命令

1)环境变量:

image.png

2)spark-shell

image.png

3)spark-sql

image.png

4)pyspark

image.png

14.提交一个简单任务

SparkPi scala代码

image.png

编译成jar包之后,使用spark-submit提交

image.png

15.spark UI

image.png

image.png

16.spark性能benchmark

TPC-DS/TPC-H benchmark github.com/databricks/…

image.png

5.2 SparkCore 原理解析

1.sparkcore

image.png

image.png

2.什么是RDD?

RDD(Resilient Distributed Dataset)

Represents an immutable, partitioned collection of elements that can be operated on in parallel.是spark中一个最基本的数据处理模型,分区决定了数量。

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() //缓存    

描述RDD的五要素

image.png

分区列表:每一个RDD都有多个分区,运行在不同节点上,每一个分区都会被一个计算任务处理。分区决定了并行计算的数量,创建RDD的时候可以指定它的分区个数,否则将按照默认值;

函数:RDD以partition为基本单位,每个RDD都会实现一个函数,对具体的partition进行计算。

依赖性:每一个RDD都会依赖于其他的RDD,每次转换都会生成一个新的RDD,进而形成一种前后依赖的关系。

实现两种类型的分区函数:基于哈希,基于范围的分区;

每个分区有一个优先的列表:尽可能将计算分配到需要处理的数据块的存储位置

算子:RDD成员函数

3.如何创建RDD

1)内置RDD

ShuffleRDD/HadoopRDD/JDBCRDD/KafkaRDD/ UnionRDD/MapPartitionsRD/...

image.png

2)自定义RDD

class CustomRDD(...) extends RDD {}

实现五要素对应的函数

4.RDD算子

➢两类RDD算子

Transform 算子:生成一个新的RDD

map/filter/flatMap/groupByKey/reduceByKey/...

image.png Action算子:触发Job提交

collect/count/take/saveAs TextFile/.

image.png

image.png

5.RDD依赖

RDD依赖:描述父子RDD之间的依赖关系(lineage)。

➢窄依赖:父RDD的每个partition至多对应一个子 RDD分区。

NarrowDependency(窄依赖)

def getParents(partitionld: Int): Seq[Int]

One ToOneDependency(一对一)

override def getParents(partitionld: Int): List[Int] = List(partitionld)

RangeDependency

overide def getParents(partitionld: Int): List[Int] = if (partitionld >= outStart && partitionld < outStart + length) List(partitionld -outStart + inStart)

PruneDependency

➢宽依赖:父RDD的每个parition都可能对应多个子RDD分区。

ShuffleDependency
partition一对一,RDD可能一对一,可能一对多

image.png

image.png

6.RDD执行流程

Job:RDD action算子触发 Stage:依据宽依赖划分 Task:Stage内执行单个partition任务

image.png

7.Scheduler

image.png

根据ShufleDependency切分Stage,并按照依赖顺序调度Stage,为每个Stage生成并提交TaskSet到TaskScheduler

根据调度算法(FIFO/FAIR)对多个TaskSet进行调度,对于调度到的TaskSet,会将Task调度(locality)到相关Executor上面执行,Executor SchedulerBackend提供

Locality: PROCESS LOCAL, NODE LOCAL, RACK LOCAL, ANY
8.Memory Management

image.png

➢Executor内存主要有两类: Storage、 Execution

➢UnifiedMemoryManager 统管理Storage/Execution内存

➢Storage 和Execution内存使用是动态调整,可以相互借用

➢当Storage 空闲,Execution 可以借用Storage的内存使用,

➢可以减少spil等操作,Execution 使用的内存不能被Storage驱逐

➢当Execution 空闲,Storage 可以借用Execution的内存使用,

➢当Execution需要内存时,可以驱逐被Storage借用的内存,直到

➢spark.memory storageFraction边界

image.png

UnifedMemoryManager统一管理多 个并发Task的内存分配每个Task获取的内存区间为1/(2*N) ~ 1/N,N为当前Executor中正在并发运行的task数量
9.Shuffle

spark.shuffle.manager -> sort

trait ShuffleManager {

def registerShuffle...

def getWriter...

def getReader..

def unregisterShuffle... } class SortShuffleManager extends ShuffleManager

image.png

SortShuffleManager

image.png

image.png

每个MapTask生成一个 Shuffle数据文件和一个index文件

dataFile中的数据按照partitionld进行排序,

同一个partitionld的数据聚集在一起

indexFile保存了所有paritionld在dataFlle中的位置信息,方便后续ReduceTask能Fetch到对应partitionld 的数据

image.png

shuffle write的文件被nodemanager中的shuffle service托管,供后续Reduce Task进行shuffle fetch,如果Executor空闲,DRA可以进行回收
5.3 SparkSQL原理解析

image.png

image.png

1.Catalyst优化器

image.png

2.Catalyst-RBO

Rule Based Optimizer(RBO)

image.png

Batch 执行策略

Once->只执行一次

FixedPoint->重复执行,直到Plan不再改变,或者执行达到固定次数(默认100次)

image.png

image.png

transformDown先序遍历树进行规则匹配 transformUp后序遍历树进行规则匹配

image.png

image.png

3.Adaptive Query Execution(AQE)(自适应)

image.png

每个Task结束会发送MapStatus信息给Driver,边查询边优化

Task的MapStatus中包含当前Task Shuffle产生的每个Partition的size统计信息

Driver获取到执行完的Stages的MapStatus信息之后,按照MapStatus中partition大小信息识别匹配一些优化场景,然后对后续未执行的Plan进行优化

目前支持的优化场景: Partiton合并, 优化shuffle读取,减少reduce task个数SMJ -> BHJ Skew Join优化

4.AQE一Coalescing Shuffle Partitions

Partition合并(coalescing shufle partitions)

image.png

问题

spark. Sql shuffle partition作业粒度参数,一个作业中所有Stage都一样,但是每个Stage实际处理的数据不一样,可能某些Stage的性能比较差,比如:partition参数对某个Stage过大,则可能单个partition的大小比较小,而且Task个数会比较多,shufle fetch阶段产生大量的小块的随机读,影响性能

parition参数对某个Stage过小,则可能单个parition的大小比较大,会产生更多的pi或者OOM

作业运行过程中,根据前面运行完的Stage的MapStatus中实际的partiton大小信息,可以将多个相邻的较小的partiton进行动态合并,由一个Task读取进行处理

spark.sql.adaptive.coalescePartitions.enabled

spark.sql.adaptive.coalescePartitions.initialPartitionNum

spark.sql.adaptive.advisoryPartitionSizelnBytes    

...

5.AQE一Switching Join Strategies

SortMergeJoin (SMJ) -> BroadcastHashJoin (BHJ)

image.png

问题 Catalyst Optimizer优化阶段,算子的statistics估算不准确,生成的执行计划并不是最优

AQE运行过程中动态获取准确Join的leftChild/rightChild的实际大小,将SMJ转换为BHJ
6.AQE - Optimizing Skew Joins

Skew Join

image.png

AQE根据MapStatus信息自动检测是否有倾斜,将大的partition拆分成多个Task进行Join

spark.sql.adaptive.skewJoin.enabled

spark.sql.adaptive.skewJoin.skewedPartitionFactor

spark.sql adaptive.skewJoin.skewedPartitionThresholdInBytes    

7.Runtime Filter

image.png

8.Bloom Runtime Filter

tpcds/q16.sql:链接 AND cs1.cs_call_center_sk=CC_call_center_sk

image.png

9.Codegen-Expression(表达式)

select (a+1)*a from t

image.png

将表达式中的大量虚函数调用压平到一个函数内部,类似手写代码

image.png

动态生成代码,Janino即时编译执行

10.Codegen一WholeStageCodegen

算子/WholeStageCodegen

select (a+1)* a fromt where a = 1

火山模型(Volcano)

image.png

算子之间大量的虚函数调用,开销大,将同一个Stage中的多个算子压平到一个函数内部进行执行

动态生成代码,Janino即时编译执行

image.png

算子/WholeStageCodegen

一个SQL包含多个Stage的WholeStageCodegen

image.png

5.4 业界挑战与实践

1.Shuffle稳定性问题

image.png

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

解决方案

image.png

2.SQL执行性能问题

image.png

➢压榨CPU资源

CPU流水线/分支预测乱序执行/SIMD/CPU缓存友好/...

Vectorized / Codegen ?

C++ / Java ?

解决方向

Python:C++实现的向量化执行引擎

image.png

Intel: OAP/gazelle_ plugin github.com/oap-project…

image.png

3.参数推荐/作业诊断

Spark参数很多,资源类/Shfle/Join/Agg/...调参难度大,参数不合理的作业,对资源利用率/Shuffle稳定性/性能有非常大影响,同时,线上作业失败运行慢,用户排查难度大->自动参数推荐/作业诊断