这是我参与「第四届青训营 」笔记创作活动的的第5天
01.大数据处理引擎Spark介绍
1.1大数据处理技术栈
- 应用:
BI报表/实时大盘/广告/推荐/...
Spark/Flink/Presto/Impala/ClickHouse/..
YARN/K8S
- 计算:
MetaStore
Parquet/ORC/DeltaLake/Hudi/Iceberg/..
HDFS/Kafka/HBase/Kudu/TOS/S3/...
- 存储:
Volume/Variety/Velocity
平台/管理/安全/...
- 数据:
On-Premise/On-Cloud
1.2 常见大数据处理链路
1.3开源大数据处理引擎
Batch:hadoop map Reduce, Apache Spark, Hive
Streaming:Flink
OLAP:presto, ClickHouse, Impala, DORIS
1.4 Spark
什么是Spark?
github: github.com/apache/spar…
Unified engine for large-scale data analytics
Apache SparkTM is a multi-language engine for
executing data engineering, data science, and
machine learning on single-node machines or clusters.
Spark 版本演进
Spark生态&特点
-
统一引擎,支持多种分布式场景
-
多语言支持
-
可读写丰富数据源
-
丰富灵活的API/算子
-
支持K8S/YARN/Mesos资源调度
1.5 Spark特点
多语言支持
SQL, Java/Scala, Python, R
丰富数据源
-
内置DataSource
Text
Parquet/ORC
JSON/CSV
JDBC
-
自定义DataSource
实现DataSourceV1/V2 API
HBase/Mongo/ElasticSearch/..
A community index of third-party packages
for Apache Spark.
丰富的API/算子
-
SparkCore -> RDD
map/filter/flatMap/mapPartitions
repartition/groupBy/reduceBy/join/aggregate
foreach/foreachPartition
count/max/min
-
SparkSQL -> DataFrame
select/filter/groupBy/agg/join/union/orderBy/..
Hive UDF/自定义UDF
1.6 Spark运行架构&部署方式
-
Spark Local Mode
本地测试/单进程多线程模式
spark-sql --master local[*] ….
-
Spark Standalone Mode
需要启动Spark的Standalone集群的Master/Worker
spark-sql --master spark://{port} …
-
On YARN/K8S
依赖外部资源调度器(YARN/K8S)
spark-sql --master yarn ...
spark-sql --master k8s://https://: ...
1.7 Spark 下载编译
Spark 包
- 编译:
git clone –b master https://github.com/apache/spark.git
cd spark
./dev/make-distribution.sh --name custom-spark --pip --r --tgz -Psparkr -Phive -Phive-thriftserver
-Pmesos -Pyarn
编译参数可选, 详见官网building-spark(spark.apache.org/docs/latest…)
编译完后会在目录下生成一个tgz包
- 下载:
官网download --> spark.apache.org/downloads.h…
1.8 Spark包概览
1.9 Spark 提交命令
环境变量
spark-shell
spark-sql
pyspark
1.10 提交一个简单任务
SparkPi scala 代码
编译成jar包之后,使用spark-submit提交
1.11 Spark UI
Job运行中-> http://port
Job运行完-> SparkHistoryServer可查看
1.12 Spark性能benchmark
TPC-DS/TPC-H benchmark
02.SparkCore 原理解析
2.1 SparkCore
2.2 什么是RDD?
RDD(Resilient Distributed Dataset)
Represents an immutable, partitioned collection of elements that can be operated on in parallel.
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的五要素
2.2.1 如何创建RDD?
- 内置RDD
ShuffleRDD/HadoopRDD/JDBCRDD
/KafkaRDD/ UnionRDD/MapPartitionsRDD/..
- 自定义RDD
class CustomRDD(...) extends RDD {}
实现五要素对应的函数
2.2.2 RDD算子
两类RDD算子
Transform算子:生成一个新的RDD
map/filter/flatMap/groupByKey/reduceByKey/..
Action算子:触发Job提交
collect/count/take/saveAsTextFile/.…
2.2.3 RDD依赖
RDD依赖:描述父子RDD之间的依赖关系(Iineage)。
-
窄依赖:父RDD的每个partition至多对应一个子RDD分区。
-
NarrowDependency
def getParents(partitionld: Int): Seq[Int]
- One ToOneDependency
override def getParents(partitionld: Int): List[Int] = List(partitionld)
- RangeDependency
override def getParents(partitionld: Int): List[Int] =
if (partitionld >= outStart && partitionld < outStart + length)
List(partitionld - outStart + inStart)
- PruneDependency
-
宽依赖:父RDD的每个partition都可能对应多个子RDD分区。
-
ShuffleDependency
2.2.4 RDD执行流程
Task: Stage 内执行单个partition 任务
Stage: 依据宽依赖划分
Job: RDD action算子触发
2.3 Scheduler
根据ShuffleDependency 切分 Stage,并按照依赖顺序调度Stage,为每个Stage生成并提交TaskSet 到TaskScheduler
根据调度算法(FIFO/FAIR)对多个TaskSet进行调度,对于调度到的TaskSet,会将Task调度(locality)到相关Executor上面执行,Executor SchedulerBackend提供
2.4 Memory Management
-
Executor内存主要有两类:Storage、Execution
-
UnifiedMemoryManager 统一管理 Storage/Execution 内存
-
Storage和Execution内存使用是动态调整,可以相互借用
-
当Storage空闲,Execution可以借用Storage的内存使用,
-
可以减少spill等操作,Execution使用的内存不能被Storage驱逐
-
当Execution空闲,Storage可以借用Execution的内存使用,
-
当Execution需要内存时,可以驱逐被Storage借用的内存,直到
-
spark.memory.storageFraction边界
UnifiedMemoryManager 统一管理多个并发Task的内存分配
每个Task获取的内存区间为1/(2*N)~1/N,N为当前Executor中正在并发运行的task数量
2.5 Shuffle
spark.shuffle.manager -> sort
trait ShuffleManager {
def registerShuffle..
def getWriter..
def getReader..
def unregisterShuffle.…
class SortShuffleManager extends ShuffleManager
SortShuffleManager
每个MapTask生成一个Shuffle数据文件和一个index文件
dataFile中的数据按照partitionld进行排序,同一个partitionld的数据聚集在一起
indexFile保存了所有paritionld在dataFlle中的位置信息,方便后续ReduceTask能Fetch到对应partitionld 的数据
External Shuffle Service + Dynamic Resource Allocation(DRA)
fetch partition-1 from MapTask-1's shuffle dataFile
shufle write 的文件被NodeManage r中的Shuffle Service 托管, 供后续ReduceTask 进行shuffle fetch, 如果Executor空闲,DRA可以进行回收
03.SparkSQL原理解析
3.1 Catalyst优化器
RBO
Rule Based Optimizer(RBO)
Batch执行策略:
Once->只执行一次
FixedPoint->重复执行,直到plan不再改变,或者执行达到固定次数(默认100次)
ConstantFolding/PushDownPredicates/ColumnPruning
/DynamicFilterPruning/SimplifyConditionals/..
object PushDownPredicates extends Rule {
def apply(plan: LogicalPlan): LogicalPlan = plan transformDown {
...
}
}
select * from t x join t1 y on x.a=y.c where x.a>1 and y.c = 2
transformDown先序遍历树进行规则匹配
transformUp后序遍历树进行规则匹配
Cost Based Optimizer(CBO)
采集表的statistics
ANALYZE TABLE ... COMPUTE STATISTICS ...FOR COLUMNS c1,c2
TableStatMANALYZE TABLE获取后续的算子的Stat通过对应的Estimation进行估算
打开参数spark.sql.cbo.enabled-> true
JoinReorder
JoinSelection
Broadcast Hash Join(BHJ)
Shuffle Hash Join(SHJ)
Sort Merge Join(SMJ)
3.2 Adaptive Query Execution(AQE)
AQE
每个Task结束会发送MapStatus信息给Driver
Task的MapStatus中包含当前Task Shuffle产生的每个Partition的size统计信息
Driver获取到执行完的Stages的MapStatus信息之后,按照MapStatus中partition大小信息识别匹配一些优化场景,然后对后续未执行的Plan进行优化
目前支持的优化场景:
-
Partiton合并,优化shuffle读取,减少reduce task个数
-
SMJ -> BHJ
-
Skew Join优化
Coalescing Shuffle Partitions
Partition 合并(coalescing shuffle partitions)
合并后:
问题
spark.sql.shufle.partition作业粒度参数,一个作业中所有Stage都一样,但是每个Stage实际处理的数据不一样,可能某些Stage的性能比较差
比如:
- partition参数对某个Stage过大,则可能单个partition的大小比
较小,而且Task个数会比较多,shuffle fetch阶段产生大量的小
块的随机读,影响性能
- parition参数对某个Stage过小,则可能单个partition的大小比
较大,会产生更多的spill或者OOM
作业运行过程中,根据前面运行完的Stage的MapStatus中实际的partiton大小信息,可以将多个相邻的较小的partiton进行动态合并,由一个Task读取进行处理
spark.sql.adaptive.coalescePartitions.enabled
spark.sql.adaptive.coalescePartitions.initialPartitionNum
spark.sql.adaptive.advisoryPartitionSizelnBytes
Switching Join Strategies
SortMergeJoin (SMJ) -> BroadcastHashJoin (BHJ)
问题:
Catalyst Optimizer优化阶段,算子的statistics估算不准确,生成的执行计划并不是最优
AQE运行过程中动态获取准确Join的leftChild/rightChild的实际大小,将SMJ转换为BHJ
Optimizing Skew Joins
Skew Join


AQE根据MapStatus信息自动检测是否有倾斜
将大的partition拆分成多个Task进行Join
spark.sql.adaptive.skewJoin.enabled
spark.sql.adaptive.skewJoin.skewedPartitionFactor
spark.sql.adaptive.skewJoin.skewedPartitionThresholdlnBytes
3.3 Runtime Filter
select x,y from A join B on A.a = B.b
3.4 Bloom Runtime Filter
链接
tpcds/q16.sql:
AND cs1.cs_call_center_sk
= cc_call_center_sk
3.5 Codegen
Expression
表达式(Expression)
将表达式中的大量虚函数调用压平到一个函数内部,类似手写代码
动态生成代码,Janino即时编译执行
WholeStageCodegen
算子/WholeStageCodegen
select (a+1) * a from t where a = 1
火山模型(Volcano)
算子之间大量的虚函数调用,开销大
将同一个Stage中的多个算子压平到一个函数内部进行执行
动态生成代码,Janino即时编译执行
一个SQL包含多个Stage的WholeStageCodegen
04.业界挑战与实践
4.1 Shuffle稳定性问题
Shuffle稳定性问题
在大规模作业下,开源ExternalShuffleService(ESS)的实现机制容易带来大量随机读导致的磁盘IOPS瓶颈、Fetch请求积压等问题,进而导致运算过程中经常会出现Stage重算甚至作业失败,继而引起资源使用的恶性循环,严重影响 SLA.
Shuffle稳定性解决方案
4.2 SQL执行性能问题
SQL执行性能问题
- 压榨CPU资源
CPU流水线/分支预测/乱序执行
/SIMD/CPU缓存友好/.…
Vectorized / Codegen ?
C++ / Java?
SQL执行性能解决方向
Photon:C++实现的向量化执行引擎
Intel: OAP/gazelle_plugin
4.3 参数推荐/作业诊断
Spark参数很多,资源类/Shuffle/Join/Agg/.…
调参难度大
参数不合理的作业,对资源利用率/Shuffle稳定性/性能有非常大影响
同时,线上作业失败/运行慢,用户排查难度大
自动参数推荐/作业诊断