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

369 阅读5分钟

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

01 大数据处理引擎Spark介绍

1.1 大数据处理技术栈

1.jpg

1.2 常见大数据处理链路

2.jpg

1.3 开源大数据处理引擎

3.jpg

1.4 什么是Spark?

(有兴趣的伙伴可以去官网看看)

官网:spark.apache.org/

github:github.com/apache/spar…

Apache Spark特点:是一个用于大规模数据处理的统一分析引擎

Apache Spark解释:是一个用于在单节点机器或集群上执行数据工程、数据科学和机器学习的多语言引擎

4.jpg

Spark版本演进

5.jpg

Spark生态 & 特点

6.jpg

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

  • 多语言支持

  • 可读写丰富数据源

  • 丰富灵活的API/算子

  • 支持K8S/YARN/Mesos资源调度

1.5 Spark特点

多语言支持

  • SQL:
SELECT
    name.first AS first_name,
    name.last AS last_name,
    age
FROM json.'logs.json'
    WHERE age > 21;
  • Java
Dataset df = spark.read().json("logs.json");
df.where("age > 21")
  .select("name.first").show();
  • Scala
Val df = spark.read.json("logs.json")
df.where("age > 21")
  .select("name.first").show()
  • R
df <- read.json(path = "logs.json")
df <- filter(df,df$age > 21)
head(select(df,df$name.first))
  • Python
df = spark.read.json("logs.json")
df.where("age > 21").select("name.first").show()

丰富数据源

内置DataSource

  • Text

  • Parquet/ORC

  • JSON/CSV

  • JDBC

自定义DataSource

  • 实现DataSourceV1/V2 API

  • HBase/Mongo/ElasticSearch/...

  • Apache Spark第三方软件包的社区索引:spark-packages.org

丰富的的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运行架构 & 部署方式

7.jpg

√ 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://<k8s-apiserver-host>:<k8s-apiserver-port> ...

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

编译参数可选,

详见官网buliding-spark(spark.apache.org/docs/latest…

编译完后会在目录下生成一个tgz包

下载:

官网download -->

8.jpg

1.8 Spark包概览

9.jpg

10.jpg

1.9 Spark提交命令

环境变量

11.jpg

spark-shell

12.jpg

spark-sql

13.jpg

pyspark

14.jpg

1.10 提交一个简单任务

SparkPi scala代码

15.jpg

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

16.jpg

17.jpg

1.11 Spark UI

18.jpg

19.jpg

1.12 Spark性能benchmark

TPC-DS / TPC-H benchmark

github.com/databricks/…

20.jpg

02 SparkCore原理解析

2.1 SparkCore

21.jpg

2.2 什么是RDD?

RDD(Resilient Distributed Dataset):表示可以并行操作的不可变分区元素集合

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的五要素

22.jpg

2.2.1 如何创建RDD?

√ 内置RDD

  • ShuffleRDD / HadoopRDD / JDBCRDD

    /KafkaRDD / UnionRDD / MapPartitionsRDD / ...

23.jpg

√ 自定义RDD

class CustomRDD(...) extends RDD{}

实现五要素对应的函数

2.2.2 RDD算子

两类RDD算子

√ Transform算子:生成一个新的RDD

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

24.jpg

√ Action算子:触发Job提交

collect / count / take / saveAsTextFile

25.jpg

26.jpg

2.2.3 RDD依赖

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

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

√ NarrowDependency

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

√ OneToOneDependency

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

27.jpg

28.jpg

2.2.4 RDD执行流程

29.jpg

2.3 调度器

30.jpg

31.jpg

2.4 内存管理

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

UnifiedMemoryManager统一管理Storage/Execution内存

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

当Storage空闲,Execution可以借用Storage内存使用,可以减少spill等操作,Execution使用的内存不能被Storage驱逐

当Execution空闲,Storage可以借用Execution内存使用,当Execution需要内存时,可以驱逐被Storage借用的内存,直到spark.memory.storageFraction边界

32.jpg

UnifiedMemoryManager统一管理多个并发Task的内存分配

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

33.jpg

2.5 Shuffle

spark.shuffle.manager -> sort

trait ShuffleManager{
  def registerShuffle...
  def getWriter...
  def getReader...
  def unregisterShuffle...
}

class SortShuffleManager extends ShuffleManager

34.jpg

2.5.1 SortShuffleManager

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

35.jpg

36.jpg

2.5.2 External Shuffle Service

External Shuffle Service + Dynamic Resource Allocation(DRA)

37.jpg

fetch partition-1 from MapTask-1's shuffle dataFile

38.jpg

shuffle write的文件被NodeManager中的Shuffle Service托管,供后续ReduceTask进行shuffle fetch,如果Executor空闲,DRA可以进行回收

03 SparkSQL原理解析

39.jpg

40.jpg

3.1 Catalyst优化器

41.jpg

Catalyst优化器 - RBO

Rule Based Optimizer(RBO)

42.jpg

Batch执行策略: Once -> 只执行一次 FixedPoint -> 重复执行,直到plan不再改变,或者执行达到固定次数(默认100次)

43.jpg


Rule Based Optimizer(RBO)

ConstantFolding / PushDownPredicates / ColumnPruning / DynamicFilterPruning / SimplifyConditionals / ...

object PushDownPredicates extends Rule{
   def apply(plan:LogicalPlan):LogicalPlan = plan transformDown{
   ...
   }
}

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

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

select * from t x join t1 y on x.a=y.c where x.a>1 and y.c = 2

44.jpg

Cost Based Optimizer(CBO)

采集表的statistics

ANALYZE TABLE...COMPUTE STATISTICS...FOR COLUMNS C1,C2

45.jpg

46.jpg

TableStat从ANALTZE TABLE获取

后续的算子的Stat通过对应的Estimation进行估算

打开参数spark.sql.cbo.enabled->true

JoinReorder

47.jpg

JoinSelection

  • Broadcast Hash Join(BHJ)

  • Shuffle Hash Join(SHJ)

  • Sort Merge Join(SMJ)

3.2 Adaptive Query Execution(AQE)

48.jpg

AQE - Coalescing Shuffle Partitions

Partition合并(coalescing shuffle partition)

49.jpg

问题:

spark.sql.shuffle.partition作业粒度参数,一个作业中所有Stage都一样,但是每个Stage实际处理的数据不一样,可能某些Stage的性能比较差

比如:

  • partition参数对某个Stage过大,则可能单个partition的大小比较小,而且Task个数会比较多,shuffle fetch阶段产生大量的小块随机读,影响性能

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

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

spark.sql.adaptive.coalescePartitions.enabled
spark.sql.adaptive.coalescePartitions.initialPartitionNum
spark.sql.adaptive.advisoryPartitionSizeInBytes
...

Switching Join Strategies

SortMergeJoin(SMJ) -> BroadcastHashJoin(BHJ)

50.jpg

问题:

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

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

Optimizing Skew Joins

51.jpg

AQE根据MapStatus信息自动检测是否有倾斜

将大的partition拆分成多个Task进行Join

spark.sql.adaptive.skewJoin.enable
spark.sql.adaptive.skewJoin.skewedPartitionFactor
spark.sql.adaptive.skewJoin.skewedPartitionThresholdlnBytres

3.3 Runtime Filter

52.jpg

3.4 Bloom Runtime Filter

tpcds/q16.sql:连接

AND cs1.cs_call_center_sk = cc_call_center_sk

53.jpg

3.5 Codegen - Expression

表达式(Expression)

54.jpg

55.jpg

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

WholeStageCodegen

算子/WholeStageCodegen

57.jpg

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


算子/WholeStageCodegen

59.jpg

60.jpg

04 业界挑战与实践

4.1 Shuffle稳定性问题

61.jpg

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

Shuffle稳定性解决方案

62.jpg

4.2 SQL执行性能问题

63.jpg

压榨CPU资源

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

Vectorized / Codegen?

C++ / Java?

4.2 SQL执行性能解决方向

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

64.jpg

Intel:OAP/gazelle_plugin

github.com/oap-project…

65.jpg

4.3 参数推荐/作业诊断

Spark参数很多,资源类/Shuffle/Join/Agg/...调参难度大

参数不合理的作业,对资源利用率/Shuffle稳定性/性能有非常大影响

同时,线上作业失败/运行慢,用户排查难度大

👇

自动参数推荐。作业诊断

05 总结

1、大数据处理常见链路,Spark特点

2、SparkCore中核心概念RDD调度机制,内存管理机制,shuffle机制

3、SQL在Spark引擎中执行的详细流程及优化方案

4、了解目前业界遇到的挑战,解决方案